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¶
- Multi-Designation Detection: Identifies all 5 protected area types (SSSI, SAC, SPA, Ramsar, NNR)
- Automatic Integration: Checks during location selection (like airspace classification)
- Graceful Degradation: API failures don't block project creation
- Persistent Storage: Protection status saved to database for audit trail
- 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:
- get_protected_areas(latitude, longitude) → dict
- Public interface for protected area checking
- Queries all 5 layers
- Aggregates results
- Generates warning messages
-
Returns standardized dict
-
_query_single_layer(lat, lon, layer_name, layer_type) → List[dict]
- Private helper for querying one WFS layer
- Builds WFS GetFeature request
- Handles HTTP request/response
- Parses GeoJSON
- 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)
-
Noneif check not performed,True/Falseif 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:
- Separate Table (ProtectedArea model) with Many-to-Many relationship
- Pros: Normalized data, supports querying by designation type
- Cons: Complex joins, migration overhead, designation data rarely changes
-
Decision: Rejected - over-engineering for read-heavy use case
-
Separate Columns per Designation Type (
in_sssi,in_sac, etc.) - Pros: Simple boolean flags, easy queries
- Cons: Loses site names/codes, inflexible (hard to add new types)
-
Decision: Rejected - insufficient detail for compliance documentation
-
JSON Storage (Selected)
- Pros: Flexible schema, stores complete details, easy to extend, consistent with
weather_forecastpattern - Cons: Harder to query by designation type (mitigated by
protected_area_statusboolean) - 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)
Integration with Consent Application System¶
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
- Project creation without protected area (Edinburgh city centre)
- Verify protected_area_status = False
-
Verify no protected_areas_data
-
Session data persistence across viability step
- POST to location with protected coordinates
- GET viability page
-
Verify template receives protected_areas_info
-
Session cleanup after project creation
- Complete project creation
- 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:
- Project Creation Flow
- [ ] Create project at protected location
- [ ] Verify protected areas panel appears on viability page
- [ ] Verify warning message displayed
- [ ] Verify badge shows correct count
-
[ ] Verify all designations listed with names and codes
-
Project View Page
- [ ] View project created with protected location
- [ ] Verify protected areas row in Project Details table
- [ ] Verify badge colour and text
-
[ ] Verify designation types comma-separated
-
Non-Protected Location
- [ ] Create project at non-protected location
- [ ] Verify NO protected areas panel on viability page
-
[ ] Verify NO protected areas row on project view page
-
Multiple Designations
- [ ] Create project at location with 2+ designations (e.g., Firth of Forth)
- [ ] Verify all designations listed
-
[ ] Verify count is correct (e.g., "2 Designations")
-
Error Handling
- [ ] Temporarily block network access (firewall rule)
- [ ] Create project
- [ ] Verify project creation succeeds (no panel shown)
-
[ ] Verify error logged
-
Performance
- [ ] Time project creation with protected area check
- [ ] 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