Skip to content

Protected Areas Classification System - Design Document

1. Executive Summary

Overview

The Protected Areas Classification System provides automatic detection and display of wildlife protection designations during drone flight planning. By integrating with the NatureScot WFS (Web Feature Service) API, the system identifies when proposed flight locations fall within environmentally sensitive areas requiring special permissions or ecological assessments.

Business Value

Environmental Compliance - Prevents inadvertent flights in protected wildlife areas - Ensures awareness of applicable environmental regulations - Supports compliance with Wildlife and Countryside Act 1981, EU Habitats/Birds Directives (retained UK law) - Reduces risk of prosecution for unauthorized disturbance

User Experience - Automatic classification (no manual research required) - Integrated into existing project creation workflow - Clear visual warnings on viability and project pages - Comprehensive information on all applicable designations

Operational Efficiency - Eliminates hours of manual research per project - Reduces errors from incomplete protection status checks - Pre-populated compliance documentation - Auditable protection status for regulatory submissions

Regulatory Adherence - Documented due diligence for environmental compliance - Evidence of pre-flight risk assessment - Supports CAA drone operation regulations (ANO 2016, as amended) - Aligns with NatureScot licensing requirements

Key Capabilities

  1. Multi-Designation Detection: Identifies all 5 protected area types (SSSI, SAC, SPA, Ramsar, NNR)
  2. Automatic Integration: Checks during location selection (like airspace classification)
  3. Graceful Degradation: API failures don't block project creation
  4. Persistent Storage: Protection status saved to database for audit trail
  5. Conditional Display: Panels shown only when location is protected

2. Business Value

Environmental Compliance

Problem Solved: Without automated checks, users may unknowingly plan flights in protected areas, risking: - Criminal prosecution under Wildlife and Countryside Act 1981 - Fines up to £40,000 or 6 months imprisonment for SSSI violations - Damage to endangered species or habitats - Reputational damage to institution

Solution Benefit: Automatic flagging of protected areas ensures users are aware of restrictions before flight planning progresses. Early awareness enables: - Timely application for NatureScot consent - Alternative location selection if permissions unlikely - Proper ecological assessment budgeting - Documentation for institutional compliance officers

User Experience

Automatic Classification: Users simply select coordinates; system handles protection status research. Saves 1-3 hours per project compared to manual research via multiple websites.

Integrated Workflow: Protection status displayed alongside airspace and urban/rural classification on the same viability page. No context switching between tools.

Pre-Populated Forms: Protection status data available for automated generation of risk assessments and consent applications (future enhancement).

Visual Clarity: Colour-coded badges, warning alerts, and structured designation lists provide at-a-glance understanding.

Operational Efficiency

Time Savings: - Manual research: 1-3 hours per project (searching NatureScot website, cross-referencing maps) - Automated check: <5 seconds per project - Annual savings (assuming 50 projects/year): 50-150 hours

Error Reduction: - Manual checks prone to missing overlapping designations - Automated system queries all 5 designation types consistently - Eliminates human error from map misinterpretation

Compliance Documentation: - Protection status automatically stored in database - Audit trail for regulatory inspections - Exportable data for reporting requirements

Regulatory Adherence

CAA Requirements (ANO 2016, Article 94A): - Drone operators must not endanger persons or property - Operators must consider environmental impact - Protection status check demonstrates due diligence

NatureScot Licensing: - Documented awareness of protected areas - Evidence of pre-flight assessment - Supports consent applications with accurate designation data

Institutional Policy: - Demonstrates responsible operation - Supports university environmental commitments - Reduces institutional liability

3. Architecture Overview

High-Level System Diagram

┌─────────────────────────────────────────────────────────────────┐
│                    User Interaction Layer                        │
│  (Project Creation Wizard - Location Selection Step)             │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             │ User submits coordinates
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                   Application Logic Layer                        │
│                                                                   │
│  ┌────────────────────┐      ┌─────────────────────┐            │
│  │ routes_new_project │─────▶│   NatureScotAPI     │            │
│  │   .py (Route)      │      │  (utils/nature_    │            │
│  │                    │◀─────│   scot_api.py)      │            │
│  └────────────────────┘      └──────────┬──────────┘            │
│           │                              │                       │
│           │                              │ 5 WFS queries        │
│           │ Session storage              │ (one per layer)      │
│           ▼                              ▼                       │
└─────────────────────────────────────────────────────────────────┘
             │                              │
             │                              │
             ▼                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                        Data Layer                                │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │          NatureScot WFS API                               │   │
│  │  https://ogc.nature.scot/geoserver/protectedareas/wfs    │   │
│  │                                                            │   │
│  │  Layers: sssi, sac, spa, ramsar, nnr                     │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
             │
             │ GeoJSON responses
             ▼
┌─────────────────────────────────────────────────────────────────┐
│                       Storage Layer                              │
│                                                                   │
│  ┌─────────────────────┐      ┌──────────────────────┐          │
│  │  Flask Session      │      │  Project Model       │          │
│  │  (Temporary)        │      │  (Database - MariaDB)│          │
│  │                     │      │                      │          │
│  │  • protected_area_  │      │  • protected_area_   │          │
│  │    status           │─────▶│    status (Boolean)  │          │
│  │  • count            │      │  • protected_areas_  │          │
│  │  • designations     │      │    data (JSON)       │          │
│  │  • warning          │      │                      │          │
│  └─────────────────────┘      └──────────────────────┘          │
└─────────────────────────────────────────────────────────────────┘
             │
             │ Template rendering
             ▼
┌─────────────────────────────────────────────────────────────────┐
│                   Presentation Layer                             │
│                                                                   │
│  ┌──────────────────────────┐   ┌───────────────────────────┐   │
│  │ Viability Page Panel     │   │ Project View Details Card │   │
│  │ (Conditional rendering)  │   │ (Conditional rendering)   │   │
│  │                          │   │                           │   │
│  │ • Badge with count       │   │ • Badge with count        │   │
│  │ • Designation list       │   │ • Comma-separated types   │   │
│  │ • Warning alert          │   │                           │   │
│  └──────────────────────────┘   └───────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Component Responsibilities

User Interaction Layer: Captures location coordinates via map interface

Application Logic Layer: Orchestrates API calls, manages session state, handles errors

Data Layer: Provides geospatial query capabilities via WFS protocol

Storage Layer: Persists classification data for audit and display

Presentation Layer: Conditionally renders protection status to users

4. Data Flow

Step-by-Step Flow

Step 1: User Selects Location Coordinates - User drags map marker or enters latitude/longitude - Submits form with project details (title, description, date, purpose) - POST request to /create_project/location

Step 2: System Calls NatureScotAPI.get_protected_areas()

# In routes_new_project.py, after airspace classification
protected_areas_data = NatureScotAPI.get_protected_areas(latitude, longitude)

Step 3: API Queries 5 WFS Layers in Sequence

# For each layer: sssi, sac, spa, ramsar, nnr
for layer_type, layer_name in cls.LAYER_NAMES.items():
    results = cls._query_single_layer(latitude, longitude, layer_name, layer_type)
    all_designations.extend(results)

Each query uses WFS GetFeature with spatial filter:

INTERSECTS(geom, POINT(longitude latitude))

Step 4: Results Aggregated and Stored in Session

session['protected_area_status'] = True  # if ANY designation found
session['protected_areas_count'] = 2
session['protected_areas_designations'] = [
    {'type': 'sssi', 'type_name': '...', 'name': '...', 'code': '...'},
    {'type': 'sac', 'type_name': '...', 'name': '...', 'code': '...'}
]
session['protected_areas_warning'] = "This location is within 2 protected area(s)..."

Step 5: Data Displayed on Viability Page - User redirected to /create_project/viability - Template receives protected_areas_info dict from session - Conditional panel rendered if in_protected_area == True

Step 6: Stored in Project Model on Creation

# User confirms viability, project created
new_project = Project(
    # ... other fields ...
    protected_area_status=session.get('protected_area_status'),
    protected_areas_data={
        'count': 2,
        'designations': [...],
        'checked_at': '2026-01-01T12:34:56Z'
    }
)
db.session.add(new_project)
db.session.commit()

Step 7: Session Cleanup

session.pop('protected_area_status', None)
session.pop('protected_areas_count', None)
session.pop('protected_areas_designations', None)
session.pop('protected_areas_warning', None)

Data Transformations

WFS Response → Application Format:

# WFS GeoJSON feature
{
    'properties': {
        'NAME': "Arthur's Seat",
        'SITE_CODE': 'UK0030150'
    }
}

# Transformed to application format
{
    'type': 'sssi',
    'type_name': 'Site of Special Scientific Interest',
    'name': "Arthur's Seat",
    'code': 'UK0030150'
}

Session → Database:

# Session (temporary, multiple keys)
session['protected_area_status'] = True
session['protected_areas_count'] = 2
session['protected_areas_designations'] = [...]

# Database (persistent, JSON field)
project.protected_area_status = True
project.protected_areas_data = {
    'count': 2,
    'designations': [...],
    'checked_at': '2026-01-01T12:34:56Z'
}

5. Component Architecture

NatureScotAPI Class (app/utils/nature_scot_api.py)

Purpose: Encapsulates all WFS API interaction logic

Key Methods:

  1. get_protected_areas(latitude, longitude) → dict
  2. Public interface for protected area checking
  3. Queries all 5 layers
  4. Aggregates results
  5. Generates warning messages
  6. Returns standardized dict

  7. _query_single_layer(lat, lon, layer_name, layer_type) → List[dict]

  8. Private helper for querying one WFS layer
  9. Builds WFS GetFeature request
  10. Handles HTTP request/response
  11. Parses GeoJSON
  12. Extracts relevant properties (NAME, SITE_CODE)

Error Handling Philosophy: - Individual layer failures: Log warning, continue with other layers - Complete failure: Return success=False, don't block project creation - Timeouts: 10 seconds per layer (same as other APIs)

Design Pattern: Static class (no instance state), following ScottishGovAPI and UKAirspaceAPI patterns

Project Model (app/models.py)

New Fields:

# NatureScot protected areas (from NatureScot WFS API)
protected_area_status = db.Column(db.Boolean, nullable=True, default=False)
protected_areas_data = db.Column(db.JSON, nullable=True)

Field Purposes:

  • protected_area_status: Boolean flag for quick filtering
  • Enables queries like: Project.query.filter_by(protected_area_status=True)
  • Indexed for performance (future enhancement)
  • None if check not performed, True/False if checked

  • protected_areas_data: JSON storage for full designation details

  • Avoids complex many-to-many relationships
  • Flexible schema (easy to add fields later)
  • Stores timestamp for audit trail

JSON Schema:

{
    'count': 2,
    'designations': [
        {
            'type': 'sssi',           # Short code
            'type_name': 'Site of Special Scientific Interest',  # Full name
            'name': "Arthur's Seat",  # Protected area name
            'code': 'UK0030150'       # Official designation code
        },
        { ... }  # Additional designations
    ],
    'checked_at': '2026-01-01T12:34:56Z'  # ISO 8601 timestamp
}

Routes Integration (app/routes_new_project.py)

API Call Location: new_project_location() POST handler, after airspace classification (line ~150)

Why This Location?: - All classification checks (urban/rural, airspace, protected areas) happen in one place - Session state established before redirect to viability page - Consistent with existing pattern

Viability Page Rendering: new_project_viability() GET handler

Prepares protected_areas_info dict from session:

protected_areas_info = {
    'in_protected_area': session.get('protected_area_status', False),
    'count': session.get('protected_areas_count', 0),
    'designations': session.get('protected_areas_designations', []),
    'warning': session.get('protected_areas_warning'),
}

Passed to template via render_template(..., protected_areas=protected_areas_info)

Project Creation: new_project_viability() POST handler

Reads session data, constructs Project object, saves to database.

UI Components

Viability Page Panel (app/templates/create_project/new_project_viability.html)

Location: Right column, after Weather Forecast Panel

Conditional Rendering:

{% if protected_areas and protected_areas.in_protected_area %}
  <!-- Panel content -->
{% endif %}

Panel Structure: - Header with leaf icon - Badge showing count of designations - List of designations (name, type, code) - Warning alert box - Data source attribution

Styling: - bg-light card background (matches other info panels) - bg-warning text-dark badges (yellow = caution) - alert-warning for permission notice - border-bottom between designation list items

Project Details Card (app/templates/dashboard/project.html)

Location: Project Details table, after Airspace row

Conditional Rendering: Same pattern (only if protected_area_status == True)

Display Format: - Badge: "2 Designations" - Small text: "Site of Special Scientific Interest, Special Area of Conservation"

Styling: bg-warning text-dark badge (consistent with viability page)

6. Protected Area Handling

Multiple Overlapping Designations Strategy

Why Show All Designations?

Many locations have overlapping protections with different legal requirements:

Example: Firth of Forth - SPA (Special Protection Area): EU Birds Directive - bird disturbance restrictions, seasonal sensitivities - Ramsar (Wetland): International treaty obligations - wetland protection - SSSI (Scientific Interest): UK law - NatureScot consent required for operations

Each designation has: - Different statutory authority - Different consent/licensing processes - Different restricted activities - Different enforcement agencies

Consequence: Users need visibility into ALL applicable designations to: - Apply for correct permissions (may need multiple consents) - Understand cumulative restrictions - Plan appropriate assessments

JSON Storage Rationale

Alternatives Considered:

  1. Separate Table (ProtectedArea model) with Many-to-Many relationship
  2. Pros: Normalized data, supports querying by designation type
  3. Cons: Complex joins, migration overhead, designation data rarely changes
  4. Decision: Rejected - over-engineering for read-heavy use case

  5. Separate Columns per Designation Type (in_sssi, in_sac, etc.)

  6. Pros: Simple boolean flags, easy queries
  7. Cons: Loses site names/codes, inflexible (hard to add new types)
  8. Decision: Rejected - insufficient detail for compliance documentation

  9. JSON Storage (Selected)

  10. Pros: Flexible schema, stores complete details, easy to extend, consistent with weather_forecast pattern
  11. Cons: Harder to query by designation type (mitigated by protected_area_status boolean)
  12. Decision: Accepted - best balance of flexibility and simplicity

JSON Storage Benefits: - Full designation details (names, codes) for documentation - Easy to add fields later (e.g., designation area, buffer zones) - Timestamp for audit trail - Consistent with existing codebase patterns

Warning Message Generation

Single Designation:

This location is within a Site of Special Scientific Interest.
Special permissions and ecological assessments may be required for flight operations.
Consult NatureScot guidance and local regulations.

Multiple Designations:

This location is within 2 protected area(s).
Special permissions and ecological assessments may be required for flight operations.
Consult NatureScot guidance and local regulations.

Purpose: Inform user of restrictions without blocking workflow. Users can proceed with project creation but are aware of compliance requirements.

7. User Interface Design

Viability Page Panel

Placement Decision: Right column with other classification panels (Urban/Rural, Airspace, Weather)

Why Right Column?: - Consistent with information-only panels - Left column = user input (viability questions) - Right column = contextual information for decision-making

Visual Hierarchy: 1. Header with icon (draws attention) 2. Badge (at-a-glance count) 3. Designation list (details) 4. Warning alert (action required) 5. Attribution (data source)

Conditional Rendering Logic:

{% if protected_areas and protected_areas.in_protected_area %}

Why Conditional?: - Most locations are NOT protected (82% of Scotland) - Showing empty panel would clutter UI - Only display actionable information

Project Details Card

Placement: Project Details table, after Airspace row (line ~87)

Row Format:

| Protected Areas: | [Badge: 2 Designations]                     |
|                  | Site of Special Scientific Interest,        |
|                  | Special Area of Conservation                |

Badge Colour: bg-warning text-dark (yellow background, dark text)

Why Yellow?: - Indicates caution (not error, not success) - Consistent with Bootstrap conventions - Matches viability page panel styling

Colour Scheme

Element Colour Bootstrap Class Meaning
Designation count badge Yellow bg-warning text-dark Caution/advisory
Warning alert Yellow alert-warning Action may be required
Header icon Blue text-primary Informational
Protected area name Grey text-muted Secondary detail

Accessibility: Yellow/dark-text combination meets WCAG AA contrast requirements (4.5:1+).

Icon Choice

Icon: fa-leaf (Font Awesome)

Rationale: - Represents nature/environment - Distinct from airspace (fa-plane), weather (fa-cloud-sun), location (fa-map-marker) - Commonly associated with environmental/ecological topics - Clear even at small sizes

8. Error Handling and Graceful Degradation

Individual Layer Failures

Scenario: SSSI layer times out, but SAC, SPA, Ramsar, NNR succeed

Behaviour:

try:
    results = cls._query_single_layer(lat, lon, 'protectedareas:sssi', 'sssi')
    all_designations.extend(results)
except Exception as layer_error:
    logger.warning(f"Failed to query sssi layer: {layer_error}")
    continue  # Keep going with other layers

Result: - Returns designations from 4 successful layers - Marks overall check as success: True - Logs warning for monitoring

Rationale: Partial data better than no data. If location is in SAC, user should know even if SSSI check failed.

Complete API Failure

Scenario: All 5 layers fail (network down, DNS failure, server error)

Behaviour:

return {
    'success': False,
    'in_protected_area': False,  # Unknown - assume not protected
    'designations': [],
    'count': 0,
    'warning': None,
    'error': 'Unexpected error: Connection timeout'
}

Session Storage:

session['protected_area_status'] = False
session['protected_areas_count'] = 0
# No designations, no warning

User Impact: No protected areas panel shown, project creation proceeds normally

Rationale: Protection status is advisory, not blocking. Users should be able to create projects even if API unavailable. Manual research remains possible.

Partial Failure Transparency

Logging Strategy:

logger.warning(f"Failed to query {layer_type} layer: {error}")
logger.info(f"Protected areas check: {count} designation(s) found (partial data)")

Monitoring: Logs enable detection of API degradation without user impact

Future Enhancement: Add admin dashboard showing API health metrics (success rate, average response time, failure reasons)

Network Timeout Handling

Timeout Setting: 10 seconds per layer (50 seconds total maximum)

Why 10 Seconds?: - Consistent with UKAirspaceAPI and ScottishGovAPI - Balance between reliability (don't fail prematurely) and UX (don't make users wait too long) - NatureScot WFS typically responds in 1-3 seconds

Timeout Behaviour:

response = requests.get(cls.WFS_ENDPOINT, params=params, timeout=cls.WFS_TIMEOUT)

Raises requests.exceptions.Timeout, caught by exception handler, layer skipped.

9. Performance Considerations

Query Time Per Layer

Typical Performance: - WFS GetFeature request: 0.5-2 seconds per layer - JSON parsing: <0.1 seconds - Total for 5 layers: 2.5-10 seconds (sequential)

Impact on User Experience: - Occurs during form submission (user expects processing time) - Parallel to weather forecast API call (no additional perceived delay if weather slower) - Acceptable for project creation workflow

Sequential vs. Parallel Query Strategy

Current Implementation: Sequential (one layer at a time)

for layer_type, layer_name in cls.LAYER_NAMES.items():
    results = cls._query_single_layer(...)  # Blocking call

Why Sequential?: - Simple implementation - Easier error handling per layer - Avoids overwhelming NatureScot server - Total time (2.5-10s) acceptable for use case

Future Optimization: Parallel queries using concurrent.futures.ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = {
        executor.submit(cls._query_single_layer, lat, lon, layer, type): type
        for type, layer in cls.LAYER_NAMES.items()
    }

Expected Improvement: 2.5-10s → 0.5-2s (limited by slowest layer)

Trade-off: Increased complexity, higher concurrent load on NatureScot server

Recommendation: Implement if user feedback indicates performance issue

Coordinate-Based Caching

Potential Optimization: Cache results by rounded coordinates

cache_key = f"{round(latitude, 4)}_{round(longitude, 4)}"  # ~11m precision

Benefits: - Repeat checks at same location (e.g., editing project) instant - Useful for clustering projects in same area

Challenges: - Cache invalidation (protected areas change rarely but do change) - Memory usage (thousands of cached locations) - Coordinate rounding precision (too coarse = incorrect cache hits)

Recommendation: Only implement if >20% of projects at repeated locations (requires usage analytics)

Database Query Performance

Current Queries:

# No additional queries - data loaded with Project
project = Project.query.get(project_id)
access project.protected_area_status  # Direct column access
access project.protected_areas_data['count']  # JSON field access

JSON Field Performance: - MariaDB JSON fields support efficient parsing - Indexed queries on JSON: Not needed (filtering by protected_area_status boolean instead)

Recommendation: Add database index if filtering/reporting by protected status becomes common:

# In future migration
batch_op.create_index('ix_project_protected_area_status', ['protected_area_status'])

10. Security and Privacy

No Personal Data in API Calls

Data Sent to NatureScot: - Latitude (e.g., 55.9445) - Longitude (e.g., -3.1619)

NOT Sent: - User information - Project details - Institution name

Privacy Impact: Minimal. Coordinates reveal flight location but not operator identity.

GDPR Compliance: No personal data processed by third party. WFS requests stateless (no tracking).

Public WFS Service (No Authentication)

Access Control: None - public OGC WFS service

Benefits: - No API keys to manage - No credential storage required - No authentication failure scenarios

Risks: - Service could be discontinued (mitigated by fallback to manual research) - Rate limiting could be imposed (mitigated by reasonable use - sequential queries)

Recommendation: Monitor NatureScot announcements for service changes.

Rate Limiting Considerations

Current Usage Pattern: - 5 WFS requests per project creation - Sequential requests (not parallel) - Expected usage: ~50 projects/month = 250 requests/month

NatureScot Policy: No published rate limits for WFS

Mitigation: - Sequential requests avoid burst load - 10-second timeout prevents runaway requests - Error handling degrades gracefully if service throttles

Future Monitoring: Log API response times; alert if >5s average (may indicate throttling)

11. Future Enhancements

Parallel Layer Queries for Performance

Current: Sequential queries (2.5-10s total) Proposed: Parallel queries using concurrent.futures (0.5-2s total) Benefit: 75-80% reduction in classification time Complexity: Medium (error handling more complex) Priority: Low (only if user feedback indicates performance issue)

Coordinate-Based Caching

Proposed: Cache API results for rounded coordinates Cache TTL: 30 days (protected areas change infrequently) Benefit: Instant results for repeated locations Complexity: Medium (cache invalidation, memory management) Priority: Low (implement after usage analytics show repeat queries >20%)

Additional Protection Types

Proposed Additions: - Local Nature Reserves (managed by councils, not NatureScot) - Marine Protected Areas (Scottish Marine Protected Area layer) - Historic Scotland sites (cultural heritage, may have drone restrictions)

Benefit: More comprehensive protection status Complexity: High (additional APIs, different data formats) Priority: Low (current 5 designations cover most critical wildlife protections)

Historical Protection Status Tracking

Proposed: Store protection status changes over time

Use Case: Project created in non-protected area; designation added later; system alerts user to new restrictions

Schema:

protection_history = db.Column(db.JSON)  # Array of {checked_at, status, designations}

Benefit: Compliance monitoring for long-running projects Complexity: High (background job to re-check all projects) Priority: Low (designations rarely change post-project creation)

Buffer Zone Warnings

Proposed: Warn if coordinates within X meters of protected area boundary

Use Case: Flight near boundary might still cause disturbance (e.g., drone noise affects birds in nearby SPA)

Technical Approach: Use WFS DWITHIN spatial filter instead of INTERSECTS

Benefit: More conservative compliance (reduce near-boundary risks) Complexity: Medium (requires tuning buffer distance, more API calls) Priority: Medium (depends on NatureScot guidance)

Proposed: Pre-populate NatureScot consent forms with project data

Fields: - Location (latitude/longitude) - Protected area names and codes - Proposed activity (drone flight) - Operator details

Benefit: Streamlined consent application process Complexity: High (NatureScot API for submissions may not exist; manual form download more likely) Priority: Low (manual form completion acceptable for infrequent consent applications)

Seasonal Sensitivity Alerts

Proposed: Highlight seasonal restrictions (e.g., SPA breeding season March-August)

Data Source: NatureScot seasonal restriction data (if available via API)

UI: Calendar widget showing restricted periods

Benefit: Proactive scheduling to avoid sensitive periods Complexity: High (requires additional data source, complex date logic) Priority: Medium (manual guidance check acceptable for now)

12. Testing Strategy

Unit Tests

File: tests/unit/test_nature_scot_api.py

Test Cases (13 total): 1. Single SSSI designation found 2. Multiple overlapping designations (SSSI + SAC) 3. No protected areas at location 4. API timeout handling 5. Network error handling 6. Partial layer failure (some succeed, some fail) 7. JSON parsing errors 8. Successful single layer query 9. Alternate property names in WFS response 10. Missing properties in response 11. Warning message for single designation 12. Warning message for multiple designations 13. Query parameter validation

Mocking Strategy:

@patch('app.utils.nature_scot_api.requests.get')
def test_get_protected_areas_single_sssi(self, mock_get):
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {'features': [...]}
    mock_get.return_value = mock_response

Coverage Goal: >90% code coverage of nature_scot_api.py

Integration Tests

File: tests/integration/test_project_creation.py (extend existing)

Test Cases: 1. Project creation with protected area (coordinates at Arthur's Seat) - Verify session storage - Verify database persistence - Verify protected_area_status = True

  1. Project creation without protected area (Edinburgh city centre)
  2. Verify protected_area_status = False
  3. Verify no protected_areas_data

  4. Session data persistence across viability step

  5. POST to location with protected coordinates
  6. GET viability page
  7. Verify template receives protected_areas_info

  8. Session cleanup after project creation

  9. Complete project creation
  10. Verify protected areas session keys removed

Testing Approach: Use live NatureScot API (not mocked) to verify real-world behaviour

Test Coordinates: - Protected: 55.9445, -3.1619 (Arthur's Seat SSSI) - Non-protected: 55.9533, -3.1883 (Edinburgh city centre)

Manual Testing Checklist

Pre-Deployment Testing:

  1. Project Creation Flow
  2. [ ] Create project at protected location
  3. [ ] Verify protected areas panel appears on viability page
  4. [ ] Verify warning message displayed
  5. [ ] Verify badge shows correct count
  6. [ ] Verify all designations listed with names and codes

  7. Project View Page

  8. [ ] View project created with protected location
  9. [ ] Verify protected areas row in Project Details table
  10. [ ] Verify badge colour and text
  11. [ ] Verify designation types comma-separated

  12. Non-Protected Location

  13. [ ] Create project at non-protected location
  14. [ ] Verify NO protected areas panel on viability page
  15. [ ] Verify NO protected areas row on project view page

  16. Multiple Designations

  17. [ ] Create project at location with 2+ designations (e.g., Firth of Forth)
  18. [ ] Verify all designations listed
  19. [ ] Verify count is correct (e.g., "2 Designations")

  20. Error Handling

  21. [ ] Temporarily block network access (firewall rule)
  22. [ ] Create project
  23. [ ] Verify project creation succeeds (no panel shown)
  24. [ ] Verify error logged

  25. Performance

  26. [ ] Time project creation with protected area check
  27. [ ] Verify total time <15 seconds (acceptable UX)

Mock Data Examples

WFS Response (SSSI found):

{
  "type": "FeatureCollection",
  "features": [{
    "properties": {
      "NAME": "Arthur's Seat",
      "SITE_CODE": "UK0030150"
    }
  }]
}

Application Response (single designation):

{
    'success': True,
    'in_protected_area': True,
    'count': 1,
    'designations': [{
        'type': 'sssi',
        'type_name': 'Site of Special Scientific Interest',
        'name': "Arthur's Seat",
        'code': 'UK0030150'
    }],
    'warning': 'This location is within a Site of Special Scientific Interest...',
    'error': None
}


Document Metadata

Version: 1.0 Date: 2026-01-01 Author: Claude Code Application: Drone Operations Management System Related Documents: - API Documentation: docs/api/nature-scot-api.md - Airspace Classification Design: docs/design/airspace-classification-design.md - Project Model Documentation: docs/models/project.md (if exists)

Change History: | Version | Date | Author | Changes | |---------|------|--------|---------| | 1.0 | 2026-01-01 | Claude Code | Initial design document |

Approval: - [ ] Technical Review - [ ] User Acceptance Testing - [ ] Documentation Review - [ ] Deployment Approved