aero-agent-roles 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +18 -0
- package/README.md +49 -0
- package/bin/aero-agent-roles.js +127 -0
- package/lib/catalog.js +97 -0
- package/lib/install.js +48 -0
- package/lib/mcp.js +199 -0
- package/manifest.json +516 -0
- package/package.json +44 -0
- package/roles/aerodynamics-engineer/ROLE.md +153 -0
- package/roles/aerodynamics-engineer/SOURCES.md +12 -0
- package/roles/aerodynamics-engineer/cli.py +143 -0
- package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
- package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
- package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
- package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
- package/roles/aircraft-design-engineer/ROLE.md +138 -0
- package/roles/aircraft-design-engineer/SOURCES.md +12 -0
- package/roles/aircraft-design-engineer/cli.py +152 -0
- package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
- package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
- package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
- package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
- package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
- package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
- package/roles/airworthiness-compliance-engineer/cli.py +146 -0
- package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
- package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
- package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
- package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
- package/roles/as9100-quality-auditor/ROLE.md +128 -0
- package/roles/as9100-quality-auditor/SOURCES.md +16 -0
- package/roles/as9100-quality-auditor/cli.py +136 -0
- package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
- package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
- package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
- package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
- package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
- package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
- package/roles/data-bus-avionics-engineer/cli.py +311 -0
- package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
- package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
- package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
- package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
- package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
- package/roles/do160-environmental-engineer/ROLE.md +136 -0
- package/roles/do160-environmental-engineer/SOURCES.md +30 -0
- package/roles/do160-environmental-engineer/cli.py +147 -0
- package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
- package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
- package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
- package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
- package/roles/do178c-cert-engineer/ROLE.md +120 -0
- package/roles/do178c-cert-engineer/SOURCES.md +16 -0
- package/roles/do178c-cert-engineer/cli.py +129 -0
- package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
- package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
- package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
- package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
- package/roles/do254-hardware-engineer/ROLE.md +127 -0
- package/roles/do254-hardware-engineer/SOURCES.md +19 -0
- package/roles/do254-hardware-engineer/cli.py +308 -0
- package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
- package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
- package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
- package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
- package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
- package/roles/engineering-analysis-engineer/ROLE.md +154 -0
- package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
- package/roles/engineering-analysis-engineer/cli.py +151 -0
- package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
- package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
- package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
- package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
- package/roles/flight-management-engineer/ROLE.md +160 -0
- package/roles/flight-management-engineer/SOURCES.md +30 -0
- package/roles/flight-management-engineer/cli.py +258 -0
- package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
- package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
- package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
- package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
- package/roles/flight-mechanics-engineer/ROLE.md +162 -0
- package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
- package/roles/flight-mechanics-engineer/cli.py +149 -0
- package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
- package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
- package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
- package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
- package/roles/flight-test-engineer/ROLE.md +137 -0
- package/roles/flight-test-engineer/SOURCES.md +24 -0
- package/roles/flight-test-engineer/cli.py +135 -0
- package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
- package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
- package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
- package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
- package/roles/flight-test-performance-engineer/ROLE.md +165 -0
- package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
- package/roles/flight-test-performance-engineer/cli.py +319 -0
- package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
- package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
- package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
- package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
- package/roles/gnc-engineer/ROLE.md +141 -0
- package/roles/gnc-engineer/SOURCES.md +35 -0
- package/roles/gnc-engineer/cli.py +141 -0
- package/roles/gnc-engineer/core/gnc_core.py +1074 -0
- package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
- package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
- package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
- package/roles/guidance-engineer/ROLE.md +156 -0
- package/roles/guidance-engineer/SOURCES.md +43 -0
- package/roles/guidance-engineer/cli.py +280 -0
- package/roles/guidance-engineer/core/guidance_core.py +820 -0
- package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
- package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
- package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
- package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
- package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
- package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
- package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
- package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
- package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
- package/roles/ndt-engineer/ROLE.md +157 -0
- package/roles/ndt-engineer/SOURCES.md +18 -0
- package/roles/ndt-engineer/cli.py +316 -0
- package/roles/ndt-engineer/core/ndt_core.py +1590 -0
- package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
- package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
- package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
- package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
- package/roles/numerical-analysis-engineer/ROLE.md +158 -0
- package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
- package/roles/numerical-analysis-engineer/cli.py +414 -0
- package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
- package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
- package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
- package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
- package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
- package/roles/propulsion-engineer/ROLE.md +163 -0
- package/roles/propulsion-engineer/SOURCES.md +15 -0
- package/roles/propulsion-engineer/cli.py +168 -0
- package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
- package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
- package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
- package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
- package/roles/space-systems-engineer/ROLE.md +161 -0
- package/roles/space-systems-engineer/SOURCES.md +49 -0
- package/roles/space-systems-engineer/cli.py +173 -0
- package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
- package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
- package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
- package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
- package/roles/state-estimation-engineer/ROLE.md +154 -0
- package/roles/state-estimation-engineer/SOURCES.md +27 -0
- package/roles/state-estimation-engineer/cli.py +326 -0
- package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
- package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
- package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
- package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
- package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
- package/roles/structures-loads-engineer/ROLE.md +171 -0
- package/roles/structures-loads-engineer/SOURCES.md +44 -0
- package/roles/structures-loads-engineer/cli.py +219 -0
- package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
- package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
- package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
- package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
- package/roles/structures-loads-engineer/tests/test_structures_loads_core.py +261 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Flight Test Plan and Envelope Expansion Report
|
|
2
|
+
|
|
3
|
+
**Aircraft:** FT-1 Worked-Example Light Jet
|
|
4
|
+
**Basis:** FAR/CS-25 context (worked example)
|
|
5
|
+
**Status:** draft-for-review
|
|
6
|
+
|
|
7
|
+
_Flight data below are SIMULATED for the worked example and must be replaced by recorded flight data._
|
|
8
|
+
|
|
9
|
+
## 1. Objectives and configuration
|
|
10
|
+
|
|
11
|
+
- Determine VS1g and the V-speed set (vref/v2/vr/VA) with warning-margin evidence in every configuration
|
|
12
|
+
- Demonstrate freedom from flutter to V_D with damping >= 0.03 at the maximum test speed and an extrapolated margin >= 1.2*V_D
|
|
13
|
+
- Expand the speed and load factor envelope in build-up blocks to V_D / n_max with a load factor check at every point
|
|
14
|
+
- Verify stall recovery characteristics within the program limits
|
|
15
|
+
- Measure climb performance and check the gradient requirement
|
|
16
|
+
|
|
17
|
+
- Limit load factor: n_max = 2.5 g positive. Stall speeds (EAS): VS1g = 44.0 m/s (86 kt), VS1 = 40.0 m/s (78 kt), VS0 = 36.0 m/s (70 kt).
|
|
18
|
+
- Placards (EAS): VFE = 62.0 m/s, VNO = 85.0 m/s, VNE = 94.0 m/s, V_D = 106.0 m/s, MMO = 0.78.
|
|
19
|
+
- Derived: VA = 69.6 m/s (135 kt), vref = 46.8 m/s (91 kt), v2 = 48.0 m/s (93 kt), vr = 44.0 m/s (86 kt).
|
|
20
|
+
- Crew: test pilot + flight test engineer console; airspace: restricted area; instrumentation: calibrated pitot-static, accelerometers, strain gauges on wing/elevator, flutter excitation vanes, telemetry with real-time damping.
|
|
21
|
+
|
|
22
|
+
## 2. Safety
|
|
23
|
+
|
|
24
|
+
| Hazard | Sev | Lik | Risk | High (>=15) | Mitigation |
|
|
25
|
+
|---|---|---|---|---|---|
|
|
26
|
+
| Flutter onset at high speed | 5 | 2 | 10 | no | GVT + build-up blocks, damping gate 0.03, excitation vanes, real-time monitoring |
|
|
27
|
+
| Stall departure at aft CG | 4 | 3 | 12 | no | entry technique, altitude floor, recovery demonstration, spin chute installed |
|
|
28
|
+
| Overspeed control upset | 4 | 2 | 8 | no | abort criteria at every block, test pilot briefing, VMO/VNE marking |
|
|
29
|
+
| Engine failure during climb segment | 3 | 2 | 6 | no | single-engine drills, field/landing options, engine instruments monitored |
|
|
30
|
+
| Telemetry data loss | 2 | 3 | 6 | no | onboard recording backup, link checked pre-flight |
|
|
31
|
+
|
|
32
|
+
Every high-risk item is mitigated before the flight: true.
|
|
33
|
+
|
|
34
|
+
| Go/no-go criterion | Value |
|
|
35
|
+
|---|---|
|
|
36
|
+
| weather | GO |
|
|
37
|
+
| aircraft_ready | GO |
|
|
38
|
+
| instrumentation_ok | GO |
|
|
39
|
+
| safety_review_ok | GO |
|
|
40
|
+
| airspace_ok | GO |
|
|
41
|
+
|
|
42
|
+
Flight release: GO.
|
|
43
|
+
|
|
44
|
+
## 3. Build-up plan
|
|
45
|
+
|
|
46
|
+
Build-up order: GVT -> flutter build-up (blocks S1..S4) -> stall matrix -> load factor envelope -> climb performance. Each block clears before the next is flown; every block states its limit, abort criteria, and PASS/FAIL criteria.
|
|
47
|
+
|
|
48
|
+
### 3.1 Flutter build-up (speed blocks as fractions of V_D)
|
|
49
|
+
|
|
50
|
+
| Block | V_D fraction | Target (EAS) | Category | Abort | PASS/FAIL criteria |
|
|
51
|
+
|---|---|---|---|---|---|
|
|
52
|
+
| S1 | 0.85 | 90.1 m/s (175 kt) | vno-to-vne | immediate pull-up + throttle reduction if buffet, LCO, or control anomaly | PASS: damping >= 0.03 at point, no LCO, load factor within 2.5 g; FAIL otherwise |
|
|
53
|
+
| S2 | 0.90 | 95.4 m/s (185 kt) | at-or-above-vne | immediate pull-up + throttle reduction if buffet, LCO, or control anomaly | PASS: damping >= 0.03 at point, no LCO, load factor within 2.5 g; FAIL otherwise |
|
|
54
|
+
| S3 | 0.95 | 100.7 m/s (196 kt) | at-or-above-vne | immediate pull-up + throttle reduction if buffet, LCO, or control anomaly | PASS: damping >= 0.03 at point, no LCO, load factor within 2.5 g; FAIL otherwise |
|
|
55
|
+
| S4 | 1.00 | 106.0 m/s (206 kt) | at-or-above-vne | immediate pull-up + throttle reduction if buffet, LCO, or control anomaly | PASS: damping >= 0.03 at point, no LCO, load factor within 2.5 g; FAIL otherwise |
|
|
56
|
+
|
|
57
|
+
Required flutter demonstration speed: V_F = 127.2 m/s (1.2 x V_D). Damping >= 0.03 required at the maximum test speed; structural mode frequency separation >= 0.10 at every point.
|
|
58
|
+
|
|
59
|
+
### 3.2 Altitude / Mach grid (V_D tested per altitude, ISA)
|
|
60
|
+
|
|
61
|
+
| Altitude | Density ratio | Speed of sound | TAS at V_D | Mach at V_D | MMO | Limit |
|
|
62
|
+
|---|---|---|---|---|---|---|
|
|
63
|
+
| 0 ft | 1.000 | 340.3 m/s | 106.0 m/s | 0.311 | 0.78 | eas-limited |
|
|
64
|
+
| 25000 ft | 0.448 | 309.7 m/s | 158.3 m/s | 0.511 | 0.78 | eas-limited |
|
|
65
|
+
| 35000 ft | 0.310 | 296.5 m/s | 190.4 m/s | 0.642 | 0.78 | eas-limited |
|
|
66
|
+
|
|
67
|
+
### 3.3 Stall matrix (VS1g and warning onset per configuration)
|
|
68
|
+
|
|
69
|
+
| Config | Measured VS1g (EAS) | Warning required | Warning onset | Recovery limits |
|
|
70
|
+
|---|---|---|---|---|
|
|
71
|
+
| clean | 44.1 m/s | 46.3 m/s | 46.8 m/s | alt <= 30 m, pitch <= 8 deg, roll <= 20 deg |
|
|
72
|
+
| takeoff (flaps) | 40.4 m/s | 42.4 m/s | 42.5 m/s | alt <= 30 m, pitch <= 8 deg, roll <= 20 deg |
|
|
73
|
+
| landing (flaps/gear) | 36.3 m/s | 38.1 m/s | 38.2 m/s | alt <= 30 m, pitch <= 8 deg, roll <= 20 deg |
|
|
74
|
+
|
|
75
|
+
### 3.4 Load factor envelope (pull blocks at VA or above)
|
|
76
|
+
|
|
77
|
+
| Block | n_max fraction | Target n | PASS/FAIL |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| L1 | 0.50 | 1.25 g | PASS if n <= 2.5 g at point, with no buffet/LCO |
|
|
80
|
+
| L2 | 0.75 | 1.88 g | PASS if n <= 2.5 g at point, with no buffet/LCO |
|
|
81
|
+
| L3 | 1.00 | 2.50 g | PASS if n <= 2.5 g at point, with no buffet/LCO |
|
|
82
|
+
|
|
83
|
+
### 3.5 Climb performance
|
|
84
|
+
|
|
85
|
+
Sawtooth climb segments at constant indicated airspeed; gradient check against 2.4 % (two-engine context).
|
|
86
|
+
|
|
87
|
+
## 4. Results
|
|
88
|
+
|
|
89
|
+
### 4.1 Flutter build-up points
|
|
90
|
+
|
|
91
|
+
| Block | Speed (EAS) | Damping | Min damping | Verdict |
|
|
92
|
+
|---|---|---|---|---|
|
|
93
|
+
| S1 | 90.1 m/s | 0.120 | 0.030 | PASS |
|
|
94
|
+
| S2 | 95.4 m/s | 0.115 | 0.030 | PASS |
|
|
95
|
+
| S3 | 100.7 m/s | 0.108 | 0.030 | PASS |
|
|
96
|
+
| S4 | 106.0 m/s | 0.100 | 0.030 | PASS |
|
|
97
|
+
|
|
98
|
+
Extrapolated flutter speed from the damping trend: 185.7 m/s; margin ratio 1.75 vs required 1.2 -> PASS.
|
|
99
|
+
|
|
100
|
+
Structural mode frequency separation (GVT pairs):
|
|
101
|
+
- mode pair 4.2 Hz / 11.6 Hz: separation 0.94 >= 0.10 -> PASS
|
|
102
|
+
- mode pair 9.1 Hz / 14.8 Hz: separation 0.48 >= 0.10 -> PASS
|
|
103
|
+
- mode pair 11.6 Hz / 14.8 Hz: separation 0.24 >= 0.10 -> PASS
|
|
104
|
+
- mode pair 4.2 Hz / 9.1 Hz: separation 0.74 >= 0.10 -> PASS
|
|
105
|
+
|
|
106
|
+
### 4.2 Stall results
|
|
107
|
+
|
|
108
|
+
| Config | VS1g (EAS) | Warning onset | Warning margin | Recovery | Verdict |
|
|
109
|
+
|---|---|---|---|---|---|
|
|
110
|
+
| clean | 44.1 m/s | 46.8 m/s | +2.70 m/s | alt 18 m / pitch 3 deg / roll 6 deg | PASS |
|
|
111
|
+
| takeoff (flaps) | 40.4 m/s | 42.5 m/s | +2.10 m/s | alt 21 m / pitch 4 deg / roll 8 deg | PASS |
|
|
112
|
+
| landing (flaps/gear) | 36.3 m/s | 38.2 m/s | +1.90 m/s | alt 24 m / pitch 5 deg / roll 9 deg | PASS |
|
|
113
|
+
|
|
114
|
+
### 4.3 Load factor envelope results
|
|
115
|
+
|
|
116
|
+
| Block | Target n | Achieved n | Limit | Verdict |
|
|
117
|
+
|---|---|---|---|---|
|
|
118
|
+
| L1 | 1.25 g | 1.26 g | 2.5 g | PASS |
|
|
119
|
+
| L2 | 1.88 g | 1.88 g | 2.5 g | PASS |
|
|
120
|
+
| L3 | 2.50 g | 2.48 g | 2.5 g | PASS |
|
|
121
|
+
|
|
122
|
+
### 4.4 Climb performance results
|
|
123
|
+
|
|
124
|
+
- Rate of climb: 1450 ft/min (segment reduction, corrected to reference weight).
|
|
125
|
+
- Gradient: 2.70 % vs 2.4 % required -> margin +0.30 % (PASS).
|
|
126
|
+
|
|
127
|
+
## 5. Findings, limit changes, and recommendations
|
|
128
|
+
|
|
129
|
+
| Envelope limit change | Traced to data point | Verdict |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| Speed envelope to 0.85 V_D | S1 | PASS |
|
|
132
|
+
| Speed envelope to 0.90 V_D | S2 | PASS |
|
|
133
|
+
| Speed envelope to 0.95 V_D | S3 | PASS |
|
|
134
|
+
| Speed envelope to 1.00 V_D | S4 | PASS |
|
|
135
|
+
|
|
136
|
+
Recommendations: (1) expand to the top block only after the damping and separation verdicts above; (2) repeat the configuration whose stall warning onset is marginal before envelope use; (3) confirm flutter clearance per point with the flight test conductor.
|
|
137
|
+
|
|
138
|
+
## 6. Sign-off
|
|
139
|
+
|
|
140
|
+
This plan/report states that the recorded data support expanding to the listed limits PENDING human flight test conductor review. It is a draft deliverable, not a clearance.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
*Generated by Aero Agent Roles flight-test-engineer core (2026-09-05). DRAFT for human flight test conductor review. Not a clearance and not an approval of the envelope or of flight.*
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test flight_test_core: the executable engine of the flight-test-engineer role.
|
|
3
|
+
|
|
4
|
+
Proves the role can DO its job standalone (no AeroSkills needed):
|
|
5
|
+
V-speed margins, corner speed, stall warning/recovery rules, flutter
|
|
6
|
+
build-up with the damping requirement and margin ratio, altitude/Mach
|
|
7
|
+
grid, safety/go-no-go, deliverable generation, and evidence-gate checks.
|
|
8
|
+
"""
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
14
|
+
"..", "core"))
|
|
15
|
+
from flight_test_core import (
|
|
16
|
+
MIN_DAMPING, MIN_FREQ_SEPARATION, accelerated_stall_speed,
|
|
17
|
+
build_flight_test_deliverable, check_flight_test_deliverable,
|
|
18
|
+
check_flight_test_markdown, classify_airspeed, corner_speed,
|
|
19
|
+
damping_margin, example_aircraft, example_deliverable_markdown,
|
|
20
|
+
example_flight_data, flutter_margin_ratio, flutter_required_speed,
|
|
21
|
+
flutter_speed_from_damping, frequency_separation, isa_density_ratio,
|
|
22
|
+
level_turn_load_factor, mach_grid_row, render_flight_test_markdown,
|
|
23
|
+
risk_index, speed_block_targets, stall_recovery_verdict,
|
|
24
|
+
stall_warning_required, stall_warning_verdict, v_speeds,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestDomainRules(unittest.TestCase):
|
|
29
|
+
|
|
30
|
+
def test_v_speed_factors(self):
|
|
31
|
+
ac = example_aircraft()
|
|
32
|
+
s = v_speeds(ac)
|
|
33
|
+
# vref = 1.3*vs0 = 46.8, v2 = 1.2*vs1 = 48.0, vr = 1.1*vs1 = 44.0
|
|
34
|
+
self.assertAlmostEqual(s["vref"], 46.8, places=3)
|
|
35
|
+
self.assertAlmostEqual(s["v2"], 48.0, places=3)
|
|
36
|
+
self.assertAlmostEqual(s["vr"], 44.0, places=3)
|
|
37
|
+
|
|
38
|
+
def test_corner_speed(self):
|
|
39
|
+
# VA = VS * sqrt(n_max): 44 * sqrt(2.5)
|
|
40
|
+
self.assertAlmostEqual(corner_speed(44.0, 2.5),
|
|
41
|
+
44.0 * 2.5 ** 0.5, places=3)
|
|
42
|
+
with self.assertRaises(ValueError):
|
|
43
|
+
corner_speed(0, 2.5)
|
|
44
|
+
with self.assertRaises(ValueError):
|
|
45
|
+
corner_speed(44.0, 1.0)
|
|
46
|
+
|
|
47
|
+
def test_placard_ordering_validation(self):
|
|
48
|
+
ac = example_aircraft()
|
|
49
|
+
self.assertAlmostEqual(v_speeds(ac)["va"], corner_speed(44.0, 2.5))
|
|
50
|
+
ac.vne_eas_ms = 50.0 # breaks vfe < va < vno < vne < vd
|
|
51
|
+
with self.assertRaises(ValueError):
|
|
52
|
+
build_flight_test_deliverable(ac)
|
|
53
|
+
|
|
54
|
+
def test_airspeed_classification(self):
|
|
55
|
+
ac = example_aircraft()
|
|
56
|
+
va = v_speeds(ac)["va"]
|
|
57
|
+
self.assertEqual(classify_airspeed(60, ac.vfe_eas_ms, va,
|
|
58
|
+
ac.vno_eas_ms, ac.vne_eas_ms),
|
|
59
|
+
"below-vfe")
|
|
60
|
+
self.assertEqual(classify_airspeed(90.1, ac.vfe_eas_ms, va,
|
|
61
|
+
ac.vno_eas_ms, ac.vne_eas_ms),
|
|
62
|
+
"vno-to-vne")
|
|
63
|
+
self.assertEqual(classify_airspeed(95.4, ac.vfe_eas_ms, va,
|
|
64
|
+
ac.vno_eas_ms, ac.vne_eas_ms),
|
|
65
|
+
"at-or-above-vne")
|
|
66
|
+
|
|
67
|
+
def test_stall_warning_margin(self):
|
|
68
|
+
# 1.05 * VS1g or VS1g + 3 kt, whichever is greater
|
|
69
|
+
req = stall_warning_required(44.0)
|
|
70
|
+
self.assertAlmostEqual(req, max(44.0 * 1.05, 44.0 + 3 * 0.514444),
|
|
71
|
+
places=3)
|
|
72
|
+
self.assertTrue(stall_warning_verdict(44.0, req + 0.1)["ok"])
|
|
73
|
+
self.assertFalse(stall_warning_verdict(44.0, req - 0.1)["ok"])
|
|
74
|
+
|
|
75
|
+
def test_accelerated_stall_and_bank(self):
|
|
76
|
+
# 60 deg bank doubles load factor: stall rises ~41%
|
|
77
|
+
n60 = level_turn_load_factor(60.0)
|
|
78
|
+
self.assertAlmostEqual(n60, 2.0, places=4)
|
|
79
|
+
v = accelerated_stall_speed(44.0, n60)
|
|
80
|
+
self.assertAlmostEqual(v, 44.0 * 2.0 ** 0.5, places=3)
|
|
81
|
+
with self.assertRaises(ValueError):
|
|
82
|
+
accelerated_stall_speed(44.0, 0.9)
|
|
83
|
+
|
|
84
|
+
def test_stall_recovery_verdict(self):
|
|
85
|
+
ac = example_aircraft()
|
|
86
|
+
good = stall_recovery_verdict(ac, 18.0, 3.0, 6.0)
|
|
87
|
+
self.assertTrue(good["ok"])
|
|
88
|
+
bad = stall_recovery_verdict(ac, 18.0, 12.0, 6.0) # pitch-up excess
|
|
89
|
+
self.assertFalse(bad["ok"])
|
|
90
|
+
self.assertFalse(bad["pitch_up_ok"])
|
|
91
|
+
|
|
92
|
+
def test_flutter_required_speed(self):
|
|
93
|
+
ac = example_aircraft()
|
|
94
|
+
self.assertAlmostEqual(flutter_required_speed(ac.vd_eas_ms),
|
|
95
|
+
1.2 * 106.0, places=3)
|
|
96
|
+
|
|
97
|
+
def test_damping_margin_rule(self):
|
|
98
|
+
self.assertTrue(damping_margin(0.100)["pass"])
|
|
99
|
+
self.assertTrue(damping_margin(0.03)["pass"]) # boundary passes
|
|
100
|
+
self.assertFalse(damping_margin(0.02)["pass"])
|
|
101
|
+
self.assertEqual(MIN_DAMPING, 0.03)
|
|
102
|
+
|
|
103
|
+
def test_frequency_separation(self):
|
|
104
|
+
r = frequency_separation(11.6, 14.8)
|
|
105
|
+
self.assertAlmostEqual(r["separation"], 3.2 / 13.2, places=3)
|
|
106
|
+
self.assertTrue(r["pass"])
|
|
107
|
+
self.assertEqual(MIN_FREQ_SEPARATION, 0.10)
|
|
108
|
+
|
|
109
|
+
def test_flutter_extrapolation_and_margin(self):
|
|
110
|
+
# simulated damping trend over the block speeds
|
|
111
|
+
vf = flutter_speed_from_damping([90.1, 95.4, 100.7, 106.0],
|
|
112
|
+
[0.120, 0.115, 0.108, 0.100])
|
|
113
|
+
self.assertGreater(vf, 106.0 * 1.2) # clears the 1.2 margin
|
|
114
|
+
m = flutter_margin_ratio(vf, 106.0)
|
|
115
|
+
self.assertTrue(m["pass"])
|
|
116
|
+
with self.assertRaises(ValueError):
|
|
117
|
+
flutter_speed_from_damping([90.1, 95.4], [0.1, 0.12]) # rising
|
|
118
|
+
|
|
119
|
+
def test_risk_index_high_threshold(self):
|
|
120
|
+
self.assertEqual(risk_index(5, 3), 15) # high risk boundary
|
|
121
|
+
self.assertEqual(risk_index(5, 2), 10) # mitigated in example
|
|
122
|
+
with self.assertRaises(ValueError):
|
|
123
|
+
risk_index(6, 1)
|
|
124
|
+
with self.assertRaises(ValueError):
|
|
125
|
+
risk_index(0, 1)
|
|
126
|
+
|
|
127
|
+
def test_mach_grid(self):
|
|
128
|
+
g0 = mach_grid_row(example_aircraft(), 0.0, 106.0)
|
|
129
|
+
self.assertAlmostEqual(g0["sigma"], 1.0, places=3)
|
|
130
|
+
self.assertLess(g0["mach"], g0["mmo"]) # EAS-limited at SL
|
|
131
|
+
# density ratio at 11000 m is the standard ~0.297 troposphere top
|
|
132
|
+
self.assertAlmostEqual(isa_density_ratio(11000.0), 0.29707,
|
|
133
|
+
places=3)
|
|
134
|
+
|
|
135
|
+
def test_speed_blocks_equal_steps_to_limit(self):
|
|
136
|
+
blocks = speed_block_targets(106.0)
|
|
137
|
+
fracs = [b["fraction"] for b in blocks]
|
|
138
|
+
self.assertEqual(fracs, [0.85, 0.90, 0.95, 1.00])
|
|
139
|
+
self.assertAlmostEqual(blocks[-1]["target_eas_ms"], 106.0)
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
speed_block_targets(106.0, (0.85, 0.95, 1.00)) # uneven steps
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestDeliverableBuilder(unittest.TestCase):
|
|
145
|
+
|
|
146
|
+
def test_example_model_complete(self):
|
|
147
|
+
model = build_flight_test_deliverable(example_aircraft())
|
|
148
|
+
self.assertEqual(model["document_type"],
|
|
149
|
+
"Flight Test Plan and Envelope Expansion Report")
|
|
150
|
+
self.assertEqual(model["status"], "draft-for-review")
|
|
151
|
+
self.assertEqual(len(model["speed_blocks"]), 4)
|
|
152
|
+
self.assertEqual(len(model["flutter"]["points"]), 4)
|
|
153
|
+
self.assertEqual(len(model["stall_rows"]), 3)
|
|
154
|
+
self.assertEqual(len(model["n_blocks"]), 3)
|
|
155
|
+
self.assertTrue(model["flutter"]["pass"])
|
|
156
|
+
self.assertTrue(model["stall_all_ok"])
|
|
157
|
+
self.assertTrue(model["go_no_go"]["go"])
|
|
158
|
+
self.assertTrue(model["safety_no_unmitigated"])
|
|
159
|
+
|
|
160
|
+
def test_every_limit_change_traces_to_data_point(self):
|
|
161
|
+
model = build_flight_test_deliverable(example_aircraft())
|
|
162
|
+
for ch in model["limit_changes"]:
|
|
163
|
+
self.assertTrue(ch["data_point"])
|
|
164
|
+
self.assertTrue(ch["ok"])
|
|
165
|
+
# each change maps to a real block id
|
|
166
|
+
ids = {b["id"] for b in model["speed_blocks"]}
|
|
167
|
+
for ch in model["limit_changes"]:
|
|
168
|
+
self.assertIn(ch["data_point"], ids)
|
|
169
|
+
|
|
170
|
+
def test_markdown_has_all_sections(self):
|
|
171
|
+
md = example_deliverable_markdown()
|
|
172
|
+
for sec in ["## 1. Objectives", "## 2. Safety", "## 3. Build-up",
|
|
173
|
+
"## 4. Results", "## 5. Findings", "## 6. Sign-off",
|
|
174
|
+
"### 3.2 Altitude / Mach grid", "### 4.1 Flutter"]:
|
|
175
|
+
self.assertIn(sec, md)
|
|
176
|
+
self.assertIn("not a clearance", md.lower())
|
|
177
|
+
self.assertIn("draft", md.lower())
|
|
178
|
+
|
|
179
|
+
def test_core_gates_all_pass(self):
|
|
180
|
+
model = build_flight_test_deliverable(example_aircraft())
|
|
181
|
+
gates = check_flight_test_deliverable(model)
|
|
182
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
183
|
+
|
|
184
|
+
def test_markdown_gates_all_pass(self):
|
|
185
|
+
md = example_deliverable_markdown()
|
|
186
|
+
gates = check_flight_test_markdown(md)
|
|
187
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
188
|
+
|
|
189
|
+
def test_sign_off_gate_fails_when_status_changed(self):
|
|
190
|
+
model = build_flight_test_deliverable(example_aircraft())
|
|
191
|
+
model["status"] = "cleared"
|
|
192
|
+
gates = check_flight_test_deliverable(model)
|
|
193
|
+
self.assertFalse(gates["all_pass"])
|
|
194
|
+
self.assertFalse(gates["sign_off_honest"])
|
|
195
|
+
|
|
196
|
+
def test_markdown_gate_fails_without_clearance_marker(self):
|
|
197
|
+
md = example_deliverable_markdown()
|
|
198
|
+
import re
|
|
199
|
+
stripped = re.sub(r"(?i)not a clearance", "noted", md)
|
|
200
|
+
self.assertNotIn("not a clearance", stripped.lower())
|
|
201
|
+
gates = check_flight_test_markdown(stripped)
|
|
202
|
+
self.assertFalse(gates["all_pass"])
|
|
203
|
+
self.assertFalse(gates["has_not_clearance"])
|
|
204
|
+
|
|
205
|
+
def test_markdown_gate_fails_with_blank_placeholders(self):
|
|
206
|
+
md = example_deliverable_markdown().replace("clean", "___clean___")
|
|
207
|
+
gates = check_flight_test_markdown(md)
|
|
208
|
+
self.assertFalse(gates["no_blank_blanks"])
|
|
209
|
+
|
|
210
|
+
def test_standalone_no_skills_repo(self):
|
|
211
|
+
# Core must work with no AeroSkills present: set env to a bad path.
|
|
212
|
+
os.environ["AEROSKILLS_DEV"] = "/nonexistent"
|
|
213
|
+
model = build_flight_test_deliverable(example_aircraft())
|
|
214
|
+
self.assertEqual(len(model["speed_blocks"]), 4)
|
|
215
|
+
md = render_flight_test_markdown(model)
|
|
216
|
+
self.assertGreater(len(md), 4000)
|
|
217
|
+
self.assertTrue(check_flight_test_markdown(md)["all_pass"])
|
|
218
|
+
|
|
219
|
+
def test_example_data_must_cover_blocks(self):
|
|
220
|
+
data = example_flight_data()
|
|
221
|
+
data["flutter_points"]["dampings"] = [0.1, 0.1, 0.1]
|
|
222
|
+
with self.assertRaises(ValueError):
|
|
223
|
+
build_flight_test_deliverable(example_aircraft(), data)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
unittest.main()
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Role test: flight-test-engineer.
|
|
3
|
+
|
|
4
|
+
Verifies (offline, no network):
|
|
5
|
+
1. Every skill bound in ROLE.md frontmatter RESOLVES to a real leaf in
|
|
6
|
+
the aero-agent-skills repo (the pinned skills_release band).
|
|
7
|
+
2. The workflow stage order is deterministic and complete.
|
|
8
|
+
3. The deliverable template is a FILLED worked example: complete
|
|
9
|
+
sections, real numbers, no ___ blanks, DRAFT + not-a-clearance.
|
|
10
|
+
4. The executable core + cli exist and pass their own gates standalone
|
|
11
|
+
(no AeroSkills checkout required).
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import unittest
|
|
17
|
+
|
|
18
|
+
ROLES_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
|
|
19
|
+
os.path.abspath(__file__)))))
|
|
20
|
+
AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
|
|
21
|
+
HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
|
|
22
|
+
ROLE_DIR = os.path.join(ROLES_REPO, "roles", "flight-test-engineer")
|
|
23
|
+
TEMPLATE = os.path.join(ROLE_DIR, "templates", "flight-test-template.md")
|
|
24
|
+
CORE = os.path.join(ROLE_DIR, "core", "flight_test_core.py")
|
|
25
|
+
CLI = os.path.join(ROLE_DIR, "cli.py")
|
|
26
|
+
|
|
27
|
+
EXPECTED_BOUND = [
|
|
28
|
+
"flight-test-operations/planning/flight-test-safety",
|
|
29
|
+
"flight-test-operations/envelope/envelope-expansion",
|
|
30
|
+
"flight-test-operations/envelope/v-speeds",
|
|
31
|
+
"flight-test-operations/envelope/load-factor-envelope",
|
|
32
|
+
"flight-test-operations/envelope/stall-characteristics-testing",
|
|
33
|
+
"flight-test-operations/envelope/high-angle-of-attack-testing",
|
|
34
|
+
"flight-test-operations/envelope/buffet-boundary-testing",
|
|
35
|
+
"flight-test-operations/envelope/spin-testing",
|
|
36
|
+
"flight-test-operations/envelope/icing-flight-test",
|
|
37
|
+
"flight-test-operations/envelope/vmc-determination",
|
|
38
|
+
"flight-test-operations/envelope/flight-loads-survey",
|
|
39
|
+
"flight-test-operations/envelope/structural-coupling-test",
|
|
40
|
+
"flight-test-operations/flutter/ground-vibration-testing",
|
|
41
|
+
"flight-test-operations/flutter/flight-vibration-survey",
|
|
42
|
+
"flight-test-operations/flutter/flutter-testing",
|
|
43
|
+
"flight-test-operations/flutter/limit-cycle-oscillation",
|
|
44
|
+
"flight-test-operations/performance/accelerate-stop-distance",
|
|
45
|
+
"flight-test-operations/performance/climb-performance-flight-test",
|
|
46
|
+
"flight-test-operations/performance/cruise-performance-flight-test",
|
|
47
|
+
"flight-test-operations/performance/engine-failure-takeoff-flight-test",
|
|
48
|
+
"flight-test-operations/performance/engine-flight-test"
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
STAGES = [
|
|
52
|
+
"1. Safety planning",
|
|
53
|
+
"2. Ground vibration",
|
|
54
|
+
"3. Flutter build-up",
|
|
55
|
+
"4. V-speeds",
|
|
56
|
+
"5. Envelope expansion",
|
|
57
|
+
"6. Stall/alpha",
|
|
58
|
+
"7. Buffet/loads",
|
|
59
|
+
"8. Spin/icing",
|
|
60
|
+
"9. Structural coupling",
|
|
61
|
+
"10. Performance",
|
|
62
|
+
"11. Report"
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def role_text():
|
|
67
|
+
p = os.path.join(ROLE_DIR, "ROLE.md")
|
|
68
|
+
return open(p).read() if os.path.exists(p) else None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestFlightTestEngineerRole(unittest.TestCase):
|
|
72
|
+
|
|
73
|
+
def test_bound_skills_resolve(self):
|
|
74
|
+
if not HAS_SKILLS:
|
|
75
|
+
self.skipTest("skills checkout absent (cross-repo dev check)")
|
|
76
|
+
for leaf in EXPECTED_BOUND:
|
|
77
|
+
self.assertTrue(
|
|
78
|
+
os.path.exists(os.path.join(AEROSKILLS, "skills", leaf, "SKILL.md")),
|
|
79
|
+
f"bound skill missing: {leaf}")
|
|
80
|
+
|
|
81
|
+
def test_stages_ordered(self):
|
|
82
|
+
role = role_text()
|
|
83
|
+
self.assertIsNotNone(role)
|
|
84
|
+
wf = role[role.find("## Workflow"):role.find("## Evidence gates")]
|
|
85
|
+
idx = [wf.find(s) for s in STAGES]
|
|
86
|
+
self.assertTrue(all(i >= 0 for i in idx), "stage missing in workflow")
|
|
87
|
+
self.assertEqual(idx, sorted(idx), "stage order not deterministic")
|
|
88
|
+
|
|
89
|
+
def test_how_to_run_present(self):
|
|
90
|
+
role = role_text()
|
|
91
|
+
self.assertIsNotNone(role)
|
|
92
|
+
for phrase in ["cli.py build", "cli.py check", "flight_test_core"]:
|
|
93
|
+
self.assertIn(phrase, role, f"missing run instruction: {phrase}")
|
|
94
|
+
|
|
95
|
+
def test_executable_core_and_cli_present(self):
|
|
96
|
+
self.assertTrue(os.path.exists(CORE), "core/flight_test_core.py missing")
|
|
97
|
+
self.assertTrue(os.path.exists(CLI), "cli.py missing")
|
|
98
|
+
# cli references the core engine
|
|
99
|
+
cli = open(CLI).read()
|
|
100
|
+
self.assertIn("flight_test_core", cli)
|
|
101
|
+
|
|
102
|
+
def test_core_runs_standalone(self):
|
|
103
|
+
# core must import and produce a passing model with no skills repo
|
|
104
|
+
old = os.environ.get("AEROSKILLS_DEV")
|
|
105
|
+
os.environ["AEROSKILLS_DEV"] = "/nonexistent"
|
|
106
|
+
try:
|
|
107
|
+
sys.path.insert(0, os.path.join(ROLE_DIR, "core"))
|
|
108
|
+
import flight_test_core as core
|
|
109
|
+
model = core.build_flight_test_deliverable(core.example_aircraft())
|
|
110
|
+
self.assertTrue(core.check_flight_test_deliverable(model)["all_pass"])
|
|
111
|
+
md = core.render_flight_test_markdown(model)
|
|
112
|
+
self.assertTrue(core.check_flight_test_markdown(md)["all_pass"])
|
|
113
|
+
finally:
|
|
114
|
+
if old is None:
|
|
115
|
+
os.environ.pop("AEROSKILLS_DEV", None)
|
|
116
|
+
else:
|
|
117
|
+
os.environ["AEROSKILLS_DEV"] = old
|
|
118
|
+
|
|
119
|
+
def test_template_complete_no_blanks(self):
|
|
120
|
+
self.assertTrue(os.path.exists(TEMPLATE), "deliverable template missing")
|
|
121
|
+
text = open(TEMPLATE).read()
|
|
122
|
+
self.assertNotIn("___", text, "template still has blank placeholders")
|
|
123
|
+
# filled deliverable sections 1..6
|
|
124
|
+
for n in range(1, 7):
|
|
125
|
+
self.assertTrue(re.search(rf"^## {n}\. ", text, re.M),
|
|
126
|
+
f"section {n} missing from template")
|
|
127
|
+
low = text.lower()
|
|
128
|
+
self.assertIn("not a clearance", low)
|
|
129
|
+
self.assertIn("draft", low)
|
|
130
|
+
self.assertIn("pass", low)
|
|
131
|
+
self.assertIn("fail", low)
|
|
132
|
+
# real numbers present (V-speeds, flutter margin, V_D)
|
|
133
|
+
self.assertIn("v_d", low)
|
|
134
|
+
self.assertTrue(re.search(r"\d+\.\d+", text))
|
|
135
|
+
|
|
136
|
+
def test_boundaries(self):
|
|
137
|
+
role = role_text()
|
|
138
|
+
self.assertIsNotNone(role)
|
|
139
|
+
low = role.lower()
|
|
140
|
+
for phrase in ["sign_off_required: true", "forbidden",
|
|
141
|
+
"never clear an envelope", "not a clearance"]:
|
|
142
|
+
self.assertIn(phrase, low, f"missing boundary: {phrase}")
|
|
143
|
+
|
|
144
|
+
def test_sources(self):
|
|
145
|
+
self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, "SOURCES.md")),
|
|
146
|
+
"SOURCES.md missing")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
unittest.main()
|