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:
is_bvlos = True— beyond visual line of sight → RPC-L2 requiredmax_altitude_agl > 120— exceeds Open category 120 m AGL ceiling → GVC or RPC-L1dropping_items = True— dropping items/payload → GVC or RPC-L1flying_over_crowds = True— flying over a crowd or public assembly → GVC or RPC-L1- 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_basicorrpc_l1— any GVC level also satisfiesgvc_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¶
-
Project creation — automatically after
db.session.commit()innew_project_viability(routes_new_project.py). Ifcategory_codeis returned, theOperationCategoryRefrow is looked up by code and assigned toproject.operation_category_id. -
Manual recalculation — via the Recalculate button in the unified editor's Category tab.
POST /dashboard/project/<id>/recalculate-categoryre-runs the calculator and commits the updatedoperation_category_id. Audit logged asrecalculate_category. -
Operational params edit —
POST /dashboard/project/<id>/edit/operation-paramssaves the five param fields and redirects to the Category tab. The user then presses Recalculate to update the stored category. Audit logged asedit_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_peopleare all present in session - The calculator is run with session data
- If the pilot fails
check_pilot_qualifications, the user is redirected tonew_project_locationwith adangerflash 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.