Skip to content

CAA Operational Category Calculation

This document describes how the system determines the correct UK CAA operational category for a drone flight, and what minimum pilot qualifications each category requires.


Background

The UK CAA defines three operational categories for UAS (drone) flights:

Category Summary
Open Low-risk operations; no prior authorisation required
Specific Higher risk; requires Operational Authorisation or PDRA
Certified Highest risk; treated similarly to manned aviation (not calculated by this system)

The Open category is further divided into three sub-categories — A1, A2, A3 — based on drone class marking, weight, and proximity to uninvolved people.


CAA Category Rules

Open – A1

Eligible drones: UK0/C0, or UK1/C1 (transitional), or unclassified with MTOW < 250 g (< 900 g transitionally)

Proximity rules: - May fly over uninvolved individuals (but not over crowds or public assemblies) - No mandatory horizontal separation from uninvolved people

Minimum pilot qualifications: - Flyer ID (required for drones ≥ 100 g) - Operator ID required if drone carries a camera and MTOW ≥ 100 g


Open – A2

Eligible drones: UK2/C2 (up to 4 kg)

Proximity rules: - Must not fly over uninvolved people - 30 m horizontal separation from uninvolved people by default - 5 m if the drone has active low-speed mode (UK2 only)

Minimum pilot qualifications: - Flyer ID - A2 Certificate of Competency (A2 CofC / GVC A2)


Open – A3

Eligible drones: UK2/C2/UK3/C3/UK4/C4 (without A2 CofC eligibility for A2), or unclassified MTOW < 25 kg

Proximity rules: - Must not fly over uninvolved people - 50 m from uninvolved individuals - 150 m from residential, commercial, industrial, or recreational areas

Minimum pilot qualifications: - Flyer ID


Specific (VLOS)

Triggered when Open category limits cannot be met but the operation stays within visual line of sight.

Minimum pilot qualifications (either suffices): - GVC (General Visual Line of Sight Certificate) — any level (Basic, Intermediate, Advanced) - RPC-L1 (Remote Pilot Certificate Level 1)


Specific (BVLOS)

Triggered when the operation goes beyond visual line of sight.

Minimum pilot qualifications: - RPC-L2 (Remote Pilot Certificate Level 2, requires 50+ flight hours)


Triggers That Force Specific Category

Any one of the following forces the operation into Specific regardless of drone class:

  1. is_bvlos = True — beyond visual line of sight → RPC-L2 required
  2. max_altitude_agl > 120 — exceeds Open category 120 m AGL ceiling → GVC or RPC-L1
  3. dropping_items = True — dropping items/payload → GVC or RPC-L1
  4. flying_over_crowds = True — flying over a crowd or public assembly → GVC or RPC-L1
  5. Drone is UK5/UK6 class or unclassified MTOW ≥ 25 kg → GVC or RPC-L1

Decision Tree

The calculator (app/utils/category_calculator.py) applies the following logic in order:

1. is_bvlos?                        → Specific (RPC-L2)
2. max_altitude_agl > 120?          → Specific (GVC/RPC-L1)
3. dropping_items?                  → Specific (GVC/RPC-L1)
4. flying_over_crowds?              → Specific (GVC/RPC-L1)
5. No drone assigned?               → None (missing_data)
6. Determine weight band:
   UK0/C0 or unclassified < 250g   → 'a1'
   UK1/C1 or unclassified < 900g   → 'a1_transitional'
   UK2/C2 or unclassified < 2000g  → 'a2'
   UK3/C3/UK4/C4 or < 25000g      → 'a3'
   UK5/UK6 or ≥ 25000g            → 'specific'
   No class mark and no MTOW       → 'unknown' (missing_data)
7. band == 'specific'?              → Specific (GVC/RPC-L1)
8. band in ('a1', 'a1_transitional'):
   → Open-A1 (required: flyer_id)
9. band == 'a2':
   proximity == 'over_people'?      → Specific
   has_a2coc AND proximity != 'over_people'? → Open-A2 (required: flyer_id, gvc_a2coc)
   no a2coc AND proximity == 'beyond_50m' AND Rural? → Open-A3 (required: flyer_id)
   otherwise                        → Specific
10. band == 'a3':
   proximity == 'over_people'?      → Specific
   proximity in ('within_30m', 'within_50m')? → Specific
   proximity == 'beyond_50m'?       → Open-A3 (required: flyer_id)
   proximity not set?               → None (missing_data)

proximity_to_people Values

Value Meaning
over_people Flying directly over uninvolved individuals
within_30m Within 30 m of uninvolved people
within_50m Between 30 m and 50 m from people
beyond_50m 50 m+ from people and 150 m+ from populated areas

Calculator Output Dictionary

calculate_operation_category(drone, project_params, pilot) returns:

Key Type Description
category str \| None 'Open-A1', 'Open-A2', 'Open-A3', 'Specific', or None
min_qualification str Human-readable minimum qualification string
required_certs list[str] Cert type codes that must be held (see AND/OR logic below)
reasons list[str] Explanation of each decision step
disqualifiers list[str] Specific conditions that prevented an Open category
missing_data list[str] Fields needed to calculate but absent

AND / OR Logic for required_certs

  • Open categories: pilot must hold all certs in required_certs (AND logic)
  • Open-A1: ['flyer_id']
  • Open-A2: ['flyer_id', 'gvc_a2coc']
  • Open-A3: ['flyer_id']
  • Specific (VLOS): pilot needs gvc_basic or rpc_l1 — any GVC level also satisfies gvc_basic (OR logic)
  • required_certs = ['gvc_basic', 'rpc_l1'] — pass if pilot holds any of: gvc_basic, gvc_intermediate, gvc_advanced, rpc_l1
  • Specific (BVLOS): pilot must hold rpc_l2 (AND logic, single cert)
  • required_certs = ['rpc_l2']

check_pilot_qualifications Output

check_pilot_qualifications(pilot_user, result) returns:

Key Type Description
passes bool True if pilot meets all requirements
missing_certs list[str] Human-readable list of missing qualifications
message str Summary string suitable for flash messages or display

Certification Type Reference

Code Name Expiry
flyer_id Flyer ID 5 years
gvc_basic GVC Basic 1 year
gvc_intermediate GVC Intermediate 1 year
gvc_advanced GVC Advanced 1 year
gvc_a2coc GVC A2 Certificate of Competency Lifetime (no expiry)
rpc_l1 RPC-L1 (Specific Category VLOS) Typically 1 year
rpc_l2 RPC-L2 (Specific Category BVLOS, 50+ hours) Typically 1 year

Where Category Is Stored

Project.operation_category_id is a nullable FK to OperationCategoryRef. Access the full record via project.operation_category (lazy='joined', so no extra query).

if project.operation_category:
    print(project.operation_category.code)            # 'Open-A2'
    print(project.operation_category.min_qualification)
    print(project.operation_category.get_badge_class())  # Bootstrap badge class

When Category Is Calculated

  1. Project creation — automatically after db.session.commit() in new_project_viability (routes_new_project.py). If category_code is returned, the OperationCategoryRef row is looked up by code and assigned to project.operation_category_id.

  2. Manual recalculation — via the Recalculate button in the unified editor's Category tab. POST /dashboard/project/<id>/recalculate-category re-runs the calculator and commits the updated operation_category_id. Audit logged as recalculate_category.

  3. Operational params editPOST /dashboard/project/<id>/edit/operation-params saves the five param fields and redirects to the Category tab. The user then presses Recalculate to update the stored category. Audit logged as edit_operation_params.


Pilot Qualification Gate (Project Creation)

In the new_project_viability GET handler, before rendering the viability page:

  • If drone, pilot, and proximity_to_people are all present in session
  • The calculator is run with session data
  • If the pilot fails check_pilot_qualifications, the user is redirected to new_project_location with a danger flash message listing the missing certifications

This is a blocking gate — the user cannot proceed to the viability step until the pilot is changed or the operational parameters are adjusted to a category the pilot qualifies for.