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
package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Equipment Environmental Qualification Plan/Report
|
|
2
|
+
|
|
3
|
+
**Item:** Terrain Awareness Warning System (TAWS) computer LRU
|
|
4
|
+
**Installation:** Forward equipment bay, pressurized and temperature-controlled, FAR/CS-25 transport
|
|
5
|
+
**Certification basis:** FAR/CS-25
|
|
6
|
+
**Standard:** DO-160G (environmental conditions and test procedures for airborne equipment)
|
|
7
|
+
**Status:** draft-for-review
|
|
8
|
+
|
|
9
|
+
## 1. Scope
|
|
10
|
+
|
|
11
|
+
This plan/report covers environmental qualification of the LRU Terrain Awareness Warning System (TAWS) computer LRU. Line-replaceable unit hosting the TAWS alerting function for a FAR/CS-25 transport.
|
|
12
|
+
Expected operating temperature extremes: -40 to 55 deg C.
|
|
13
|
+
|
|
14
|
+
## 2. Environmental categories
|
|
15
|
+
|
|
16
|
+
- Temperature/altitude category: B1 (typical operating range -55 to 55 deg C; typical reference data, confirm against the current revision).
|
|
17
|
+
- ESD category: A (single category per Section 25).
|
|
18
|
+
- RF emissions installation category: A (reference-only RE102 floor model).
|
|
19
|
+
- Lightning: Section 22 test level 3, waveform set A, B, C, H.
|
|
20
|
+
- Power input: 28 VDC bus, Section 16 envelope (see Section 6).
|
|
21
|
+
|
|
22
|
+
## 3. Qualification test matrix
|
|
23
|
+
|
|
24
|
+
Required test-condition sections for the equipment category (B1) and planned coverage:
|
|
25
|
+
|
|
26
|
+
| Section | Test condition | Planned |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| 4 | Temperature and altitude | yes |
|
|
29
|
+
| 5 | Temperature variation | yes |
|
|
30
|
+
| 6 | Humidity | yes |
|
|
31
|
+
| 7 | Operational shocks and crash safety | yes |
|
|
32
|
+
| 8 | Vibration | yes |
|
|
33
|
+
| 9 | Explosion proofness | yes |
|
|
34
|
+
| 10 | Waterproofness | yes |
|
|
35
|
+
| 11 | Fluids susceptibility | yes |
|
|
36
|
+
| 16 | Power input | yes |
|
|
37
|
+
| 19 | Induced signal susceptibility | yes |
|
|
38
|
+
| 20 | Radio frequency susceptibility | yes |
|
|
39
|
+
| 21 | Emission of radio frequency energy | yes |
|
|
40
|
+
| 22 | Lightning induced transient susceptibility | yes |
|
|
41
|
+
| 23 | Lightning direct effects | yes |
|
|
42
|
+
| 24 | Icing | yes |
|
|
43
|
+
| 25 | Electrostatic discharge | yes |
|
|
44
|
+
|
|
45
|
+
Matrix completeness: COMPLETE - no required section missing.
|
|
46
|
+
Category-specific section exclusions must be confirmed against the current revision before freezing the matrix.
|
|
47
|
+
|
|
48
|
+
## 4. Electrostatic discharge (Section 25)
|
|
49
|
+
|
|
50
|
+
- Equipment category: A (the only Section 25 category).
|
|
51
|
+
- Test level: 15 kV air discharge on the equipment bonded to the ground plane.
|
|
52
|
+
- Discharge generator (IEC 61000-4-2 human-body model): 150 pF storage capacitance, 330 ohm discharge resistance.
|
|
53
|
+
- Stored energy at test level: 0.016875 J (16.875 mJ).
|
|
54
|
+
- Waveform: first peak 56.25 A, 30.00 A at 30 ns, 15.00 A at 60 ns; rise time 0.7 to 1.0 ns; RC time constant 49.5 ns.
|
|
55
|
+
- Discharges per test point: 10 positive and 10 negative (valid: true).
|
|
56
|
+
- Test points: surfaces accessible to personnel during normal operation or maintenance; connector pins are not applicable test points.
|
|
57
|
+
- Verdict (planned): PASS - operates as specified with no permanent degradation.
|
|
58
|
+
|
|
59
|
+
## 5. Lightning protection (Sections 22/23)
|
|
60
|
+
|
|
61
|
+
- Section 22 induced transient susceptibility test level: 3 (valid range 1-5: true).
|
|
62
|
+
- Waveform set: A, B, C, H (letters within A-H: true).
|
|
63
|
+
- Section 22/23 pass criteria: no physical damage, no upset, no latch-up after the applied transients.
|
|
64
|
+
- Verdict (planned): PASS.
|
|
65
|
+
- Level and waveform tables are standard data in the current revision; verify selection before freezing the plan.
|
|
66
|
+
|
|
67
|
+
## 6. Power input (Section 16)
|
|
68
|
+
|
|
69
|
+
- Bus: 28 VDC; Section 16 category envelope values applied from the current revision (data-driven, summary reference).
|
|
70
|
+
- Normal steady-state range: 22.0 to 29.0 V. Emergency range: 18.0 to 32.2 V (emergency-rated: true).
|
|
71
|
+
- Measured steady state 27.5 V: within normal range; margins 5.5 V low / 1.5 V high.
|
|
72
|
+
- Measured sag 20.0% of nominal (80 ms event vs 25% max / 100 ms envelope): within envelope; margins 20.0 ms, 5.0%.
|
|
73
|
+
- Recovery after transient 60 ms vs 100 ms allowable: within.
|
|
74
|
+
- Ripple: 3.57% of nominal (characterization).
|
|
75
|
+
- Emergency classification of measured bus: normal.
|
|
76
|
+
|
|
77
|
+
## 7. Radio frequency susceptibility (Section 20)
|
|
78
|
+
|
|
79
|
+
- RS103 radiated test field: 100 V/m (typical mid-range category value; verify against the current revision).
|
|
80
|
+
- Calibration field with 6 dB margin: 199.5 V/m.
|
|
81
|
+
- Amplifier budget for 3 m, 3 dBi, 3 dB cable loss, 6 dB margin: 11943 W.
|
|
82
|
+
- Far-field check: wavelength at 1 GHz is 0.2998 m; the far-field relation E = sqrt(30*P*G)/d holds beyond the Fraunhofer distance.
|
|
83
|
+
- CS114 conducted category C: limit 75.7 dBuA (summary reference base); measured 60.0 dBuA gives margin 15.7 dB (within limit).
|
|
84
|
+
|
|
85
|
+
## 8. Radio frequency emissions (Section 21)
|
|
86
|
+
|
|
87
|
+
- RE102 radiated, installation category A (reference-only floor 24 dBuV/m, 2 MHz to 18 GHz):
|
|
88
|
+
- Measured sweep: 15.0 dBuV/m at 10 MHz, 20.0 dBuV/m at 100 MHz, 18.0 dBuV/m at 1 GHz.
|
|
89
|
+
- Margins: +9.0 dB, +4.0 dB, +6.0 dB; worst case 100 MHz at +4.0 dB.
|
|
90
|
+
- Verdict: PASS (minimum margin >= 0 dB).
|
|
91
|
+
- CE102 conducted (10 kHz to 10 MHz, reference-only band curve):
|
|
92
|
+
- Measured sweep: 68.0 dBuV at 50 kHz, 54.0 dBuV at 150 kHz, 66.0 dBuV at 5 MHz.
|
|
93
|
+
- Margins: +10.0 dB, +6.0 dB, +4.0 dB; worst case 5 MHz at +4.0 dB.
|
|
94
|
+
- Verdict: PASS (minimum margin >= 0 dB).
|
|
95
|
+
- A margin band of at least 6 dB is typical engineering recommendation (reference-only, not an RTCA requirement).
|
|
96
|
+
|
|
97
|
+
## 9. Verdicts and open items
|
|
98
|
+
|
|
99
|
+
| Domain | Section | Verdict |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| Electrostatic discharge | 25 | PASS |
|
|
102
|
+
| Lightning | 22/23 | PASS |
|
|
103
|
+
| Power input | 16 | PASS |
|
|
104
|
+
| RF susceptibility (CS114) | 20 | PASS |
|
|
105
|
+
| RF emissions (RE102) | 21 | PASS |
|
|
106
|
+
| RF emissions (CE102) | 21 | PASS |
|
|
107
|
+
|
|
108
|
+
Open items before the human environmental qualification engineer signs:
|
|
109
|
+
- Confirm level/waveform/limit tables against the current DO-160 revision (typical reference data used throughout).
|
|
110
|
+
- Confirm category-specific matrix exclusions for the installed location.
|
|
111
|
+
- Review measured bench data against the qualification test environment.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
*Generated by Aero Agent Roles do160-environmental-engineer core. DRAFT for human environmental qualification engineer review. Not an approval document.*
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test do160_environmental_core: the executable engine of the DO-160G
|
|
3
|
+
Environmental Qualification Engineer role.
|
|
4
|
+
|
|
5
|
+
Proves the role can DO its job standalone (no AeroSkills needed):
|
|
6
|
+
category selection, matrix completeness, ESD/lightning/power/RF domain
|
|
7
|
+
rules, report generation, and evidence-gate checks. Every numeric
|
|
8
|
+
anchor mirrors the bound AeroSkills avionics/do160 leaf logic files.
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import unittest
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
15
|
+
"..", "core"))
|
|
16
|
+
from do160_environmental_core import ( # noqa: E402
|
|
17
|
+
CE102_BAND_HI_HZ, CE102_BAND_LO_HZ,
|
|
18
|
+
RE102_BAND_HI_HZ, RE102_BAND_LO_HZ, RE102_FLOOR_DBU_VPM,
|
|
19
|
+
SECTIONS, TEMPERATURE_RANGES,
|
|
20
|
+
build_qualification_report, check_qualification_report,
|
|
21
|
+
check_qualification_report_markdown,
|
|
22
|
+
cs114_category_offset, cs114_limit_dbu_a, dbu_v_per_m_from_v_per_m,
|
|
23
|
+
dbu_v_from_volts, emission_verdict, esd_category_test_level_kv,
|
|
24
|
+
esd_current_30ns_amps, esd_current_60ns_amps, esd_discharge_count_valid,
|
|
25
|
+
esd_pass_verdict, esd_peak_current_amps, esd_rc_time_constant_ns,
|
|
26
|
+
esd_rise_time_valid_ns, esd_stored_energy_joules,
|
|
27
|
+
esd_test_point_applicable, example_item,
|
|
28
|
+
example_qualification_report_markdown, field_strength_from_erp,
|
|
29
|
+
frequency_deviation, frequency_within_tolerance, lightning_level_valid,
|
|
30
|
+
lightning_pass_verdict, lightning_waveform_valid, limits_margins,
|
|
31
|
+
margin_check_dbu, matrix_complete, power_for_field_strength,
|
|
32
|
+
required_amp_power_for_test, required_sections, re102_limit_db,
|
|
33
|
+
render_qualification_report_markdown, ripple_percent,
|
|
34
|
+
sag_depth_percent, section_name, select_temperature_category,
|
|
35
|
+
surge_height_percent, temperature_category_range, temp_within_range,
|
|
36
|
+
transient_check, transient_recovery_ok, voltage_within_limits,
|
|
37
|
+
worst_case_frequency,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestEnvironmentalQualificationDomain(unittest.TestCase):
|
|
42
|
+
"""Mirrors avionics/do160/environmental-qualification logic anchors."""
|
|
43
|
+
|
|
44
|
+
def test_section_names(self):
|
|
45
|
+
self.assertEqual(section_name(16), "Power input")
|
|
46
|
+
self.assertEqual(section_name(25), "Electrostatic discharge")
|
|
47
|
+
self.assertEqual(section_name(20), "Radio frequency susceptibility")
|
|
48
|
+
self.assertEqual(section_name(21), "Emission of radio frequency energy")
|
|
49
|
+
self.assertEqual(section_name(22),
|
|
50
|
+
"Lightning induced transient susceptibility")
|
|
51
|
+
with self.assertRaises(ValueError):
|
|
52
|
+
section_name(99)
|
|
53
|
+
|
|
54
|
+
def test_temperature_ranges(self):
|
|
55
|
+
self.assertEqual(temperature_category_range("B1"), (-55, 55))
|
|
56
|
+
self.assertEqual(temperature_category_range("A2"), (-55, 70))
|
|
57
|
+
self.assertEqual(TEMPERATURE_RANGES["B2"], (-55, 70))
|
|
58
|
+
with self.assertRaises(ValueError):
|
|
59
|
+
temperature_category_range("Z9")
|
|
60
|
+
|
|
61
|
+
def test_temp_within_range(self):
|
|
62
|
+
self.assertTrue(temp_within_range(-40, "B1"))
|
|
63
|
+
self.assertTrue(temp_within_range(55, "B1")) # inclusive top
|
|
64
|
+
self.assertFalse(temp_within_range(60, "B1"))
|
|
65
|
+
|
|
66
|
+
def test_category_selection_covers_extremes(self):
|
|
67
|
+
# example LRU: -40..55 deg C in a pressurized bay -> B1 covers
|
|
68
|
+
cat = select_temperature_category(-40, 55)
|
|
69
|
+
self.assertEqual(cat, "B1")
|
|
70
|
+
lo, hi = temperature_category_range(cat)
|
|
71
|
+
self.assertLessEqual(-40, hi) and self.assertGreaterEqual(55, lo)
|
|
72
|
+
# any declared extremes must be inside the selected typical range
|
|
73
|
+
cat2 = select_temperature_category(-55, 70)
|
|
74
|
+
self.assertTrue(temp_within_range(-55, cat2))
|
|
75
|
+
self.assertTrue(temp_within_range(70, cat2))
|
|
76
|
+
|
|
77
|
+
def test_required_sections_full_set(self):
|
|
78
|
+
req = required_sections("B1")
|
|
79
|
+
# full leaf section set, sorted
|
|
80
|
+
self.assertEqual(req, sorted(SECTIONS))
|
|
81
|
+
self.assertIn(22, req) # lightning induced - never omitted by default
|
|
82
|
+
self.assertIn(25, req) # ESD
|
|
83
|
+
|
|
84
|
+
def test_matrix_complete(self):
|
|
85
|
+
missing, ok = matrix_complete(sorted(SECTIONS), "B1")
|
|
86
|
+
self.assertEqual(missing, [])
|
|
87
|
+
self.assertTrue(ok)
|
|
88
|
+
missing2, ok2 = matrix_complete([4, 5, 6], "B1")
|
|
89
|
+
self.assertIn(25, missing2)
|
|
90
|
+
self.assertFalse(ok2)
|
|
91
|
+
with self.assertRaises(ValueError):
|
|
92
|
+
matrix_complete([99], "B1")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestEsdDomain(unittest.TestCase):
|
|
96
|
+
"""Mirrors avionics/do160/electrostatic-discharge logic anchors."""
|
|
97
|
+
|
|
98
|
+
def test_category_level(self):
|
|
99
|
+
self.assertEqual(esd_category_test_level_kv("A"), 15.0)
|
|
100
|
+
self.assertEqual(esd_category_test_level_kv("a"), 15.0)
|
|
101
|
+
with self.assertRaises(ValueError):
|
|
102
|
+
esd_category_test_level_kv("B")
|
|
103
|
+
|
|
104
|
+
def test_stored_energy_anchors(self):
|
|
105
|
+
# 150 pF at 15 kV -> 16.875 mJ; at 8 kV -> 4.8 mJ
|
|
106
|
+
self.assertAlmostEqual(esd_stored_energy_joules(150.0, 15.0),
|
|
107
|
+
0.016875, places=9)
|
|
108
|
+
self.assertAlmostEqual(esd_stored_energy_joules(150.0, 8.0),
|
|
109
|
+
0.0048, places=9)
|
|
110
|
+
|
|
111
|
+
def test_waveform_current_anchors(self):
|
|
112
|
+
self.assertAlmostEqual(esd_peak_current_amps(15.0), 56.25, places=2)
|
|
113
|
+
self.assertAlmostEqual(esd_peak_current_amps(2.0), 7.5, places=2)
|
|
114
|
+
self.assertAlmostEqual(esd_current_30ns_amps(15.0), 30.0, places=2)
|
|
115
|
+
self.assertAlmostEqual(esd_current_60ns_amps(15.0), 15.0, places=2)
|
|
116
|
+
|
|
117
|
+
def test_rise_and_rc(self):
|
|
118
|
+
self.assertTrue(esd_rise_time_valid_ns(0.8))
|
|
119
|
+
self.assertFalse(esd_rise_time_valid_ns(0.5))
|
|
120
|
+
self.assertFalse(esd_rise_time_valid_ns(1.2))
|
|
121
|
+
self.assertAlmostEqual(esd_rc_time_constant_ns(330.0, 150.0),
|
|
122
|
+
49.5, places=1)
|
|
123
|
+
|
|
124
|
+
def test_discharge_counts(self):
|
|
125
|
+
self.assertTrue(esd_discharge_count_valid(10, 10))
|
|
126
|
+
self.assertFalse(esd_discharge_count_valid(9, 10))
|
|
127
|
+
self.assertFalse(esd_discharge_count_valid(10, 9))
|
|
128
|
+
with self.assertRaises(ValueError):
|
|
129
|
+
esd_discharge_count_valid(10.5, 10)
|
|
130
|
+
|
|
131
|
+
def test_test_point_applicability(self):
|
|
132
|
+
self.assertTrue(esd_test_point_applicable(True, False, False))
|
|
133
|
+
self.assertTrue(esd_test_point_applicable(False, True, False))
|
|
134
|
+
# connector pins are NOT applicable test points (DO-160G)
|
|
135
|
+
self.assertFalse(esd_test_point_applicable(True, False, True))
|
|
136
|
+
self.assertFalse(esd_test_point_applicable(False, False, False))
|
|
137
|
+
|
|
138
|
+
def test_pass_verdict(self):
|
|
139
|
+
self.assertTrue(esd_pass_verdict(True, True))
|
|
140
|
+
self.assertFalse(esd_pass_verdict(False, True))
|
|
141
|
+
self.assertFalse(esd_pass_verdict(True, False))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestLightningDomain(unittest.TestCase):
|
|
145
|
+
"""Mirrors avionics/do160/lightning-protection logic anchors."""
|
|
146
|
+
|
|
147
|
+
def test_level_range(self):
|
|
148
|
+
self.assertTrue(lightning_level_valid(3))
|
|
149
|
+
self.assertTrue(lightning_level_valid(1))
|
|
150
|
+
self.assertTrue(lightning_level_valid(5))
|
|
151
|
+
self.assertFalse(lightning_level_valid(0))
|
|
152
|
+
self.assertFalse(lightning_level_valid(6))
|
|
153
|
+
with self.assertRaises(ValueError):
|
|
154
|
+
lightning_level_valid("3")
|
|
155
|
+
with self.assertRaises(ValueError):
|
|
156
|
+
lightning_level_valid(3.5)
|
|
157
|
+
|
|
158
|
+
def test_waveforms(self):
|
|
159
|
+
for w in "ABCDEFGH":
|
|
160
|
+
self.assertTrue(lightning_waveform_valid(w))
|
|
161
|
+
self.assertTrue(lightning_waveform_valid("c")) # case-insensitive
|
|
162
|
+
self.assertFalse(lightning_waveform_valid("I"))
|
|
163
|
+
self.assertFalse(lightning_waveform_valid(""))
|
|
164
|
+
with self.assertRaises(ValueError):
|
|
165
|
+
lightning_waveform_valid(7)
|
|
166
|
+
|
|
167
|
+
def test_pass_verdict(self):
|
|
168
|
+
self.assertTrue(lightning_pass_verdict(False, False, False))
|
|
169
|
+
self.assertFalse(lightning_pass_verdict(True, False, False))
|
|
170
|
+
self.assertFalse(lightning_pass_verdict(False, True, False))
|
|
171
|
+
self.assertFalse(lightning_pass_verdict(False, False, True))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestPowerInputDomain(unittest.TestCase):
|
|
175
|
+
"""Mirrors avionics/do160/power-input logic anchors (data-driven)."""
|
|
176
|
+
|
|
177
|
+
def test_sag_surge(self):
|
|
178
|
+
self.assertAlmostEqual(sag_depth_percent(28.0, 21.0), 25.0, places=6)
|
|
179
|
+
self.assertAlmostEqual(surge_height_percent(28.0, 32.2), 15.0,
|
|
180
|
+
places=6)
|
|
181
|
+
with self.assertRaises(ValueError):
|
|
182
|
+
sag_depth_percent(28.0, 32.0) # that is a surge
|
|
183
|
+
|
|
184
|
+
def test_frequency(self):
|
|
185
|
+
dev_hz, dev_pct = frequency_deviation(412.0, 400.0)
|
|
186
|
+
self.assertAlmostEqual(dev_hz, 12.0, places=6)
|
|
187
|
+
self.assertAlmostEqual(dev_pct, 3.0, places=6)
|
|
188
|
+
self.assertTrue(frequency_within_tolerance(412.0, 400.0, 5.0))
|
|
189
|
+
self.assertFalse(frequency_within_tolerance(422.0, 400.0, 5.0))
|
|
190
|
+
|
|
191
|
+
def test_limits_and_margins(self):
|
|
192
|
+
self.assertTrue(voltage_within_limits(27.5, 22.0, 29.0))
|
|
193
|
+
self.assertFalse(voltage_within_limits(21.0, 22.0, 29.0))
|
|
194
|
+
ml, mh = limits_margins(27.5, 22.0, 29.0)
|
|
195
|
+
self.assertAlmostEqual(ml, 5.5, places=6)
|
|
196
|
+
self.assertAlmostEqual(mh, 1.5, places=6)
|
|
197
|
+
|
|
198
|
+
def test_transients(self):
|
|
199
|
+
self.assertTrue(transient_recovery_ok(60.0, 100.0))
|
|
200
|
+
ok, dm, dp = transient_check(80.0, 20.0, 100.0, 25.0)
|
|
201
|
+
self.assertTrue(ok)
|
|
202
|
+
self.assertAlmostEqual(dm, 20.0, places=6)
|
|
203
|
+
self.assertAlmostEqual(dp, 5.0, places=6)
|
|
204
|
+
|
|
205
|
+
def test_ripple(self):
|
|
206
|
+
self.assertAlmostEqual(ripple_percent(29.0, 27.0, 28.0),
|
|
207
|
+
3.5714285714, places=6)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class TestRfSusceptibilityDomain(unittest.TestCase):
|
|
211
|
+
"""Mirrors avionics/do160/radio-frequency-susceptibility anchors."""
|
|
212
|
+
|
|
213
|
+
def test_amp_budget_anchor(self):
|
|
214
|
+
# 100 V/m at 3 m, 3 dBi, 3 dB cable loss, 6 dB margin -> ~12 kW
|
|
215
|
+
w = required_amp_power_for_test(100.0, 3.0, 3.0, 3.0, 6.0)
|
|
216
|
+
self.assertAlmostEqual(w, 11970.0, delta=120)
|
|
217
|
+
|
|
218
|
+
def test_power_roundtrip(self):
|
|
219
|
+
# E = sqrt(30*P*G)/d; P = E^2 d^2 / (30 G)
|
|
220
|
+
self.assertAlmostEqual(power_for_field_strength(5.477, 1.0, 10.0),
|
|
221
|
+
100.0, delta=0.1)
|
|
222
|
+
|
|
223
|
+
def test_cs114_category_steps(self):
|
|
224
|
+
self.assertEqual(cs114_category_offset("A"), 0.0)
|
|
225
|
+
self.assertEqual(cs114_category_offset("B"), 10.0)
|
|
226
|
+
self.assertEqual(cs114_category_offset("C"), 20.0)
|
|
227
|
+
self.assertEqual(cs114_category_offset("J"), 80.0)
|
|
228
|
+
with self.assertRaises(ValueError):
|
|
229
|
+
cs114_category_offset("I") # letter I is skipped
|
|
230
|
+
self.assertAlmostEqual(cs114_limit_dbu_a("B"), 65.7, places=1)
|
|
231
|
+
|
|
232
|
+
def test_margin_check(self):
|
|
233
|
+
margin, ok = margin_check_dbu(60.0, 65.7)
|
|
234
|
+
self.assertTrue(ok)
|
|
235
|
+
self.assertAlmostEqual(margin, 5.7, places=1)
|
|
236
|
+
_, bad = margin_check_dbu(70.0, 65.7)
|
|
237
|
+
self.assertFalse(bad)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TestRfEmissionsDomain(unittest.TestCase):
|
|
241
|
+
"""Mirrors avionics/do160/radio-frequency-emissions anchors."""
|
|
242
|
+
|
|
243
|
+
def test_conversions(self):
|
|
244
|
+
self.assertAlmostEqual(dbu_v_from_volts(1.0), 120.0, places=6)
|
|
245
|
+
self.assertAlmostEqual(dbu_v_per_m_from_v_per_m(1.0), 120.0,
|
|
246
|
+
places=6)
|
|
247
|
+
|
|
248
|
+
def test_limit_curves(self):
|
|
249
|
+
self.assertEqual(RE102_FLOOR_DBU_VPM, {"A": 24.0, "B": 34.0,
|
|
250
|
+
"C": 44.0})
|
|
251
|
+
self.assertAlmostEqual(re102_limit_db(100e6, "A"), 24.0, places=6)
|
|
252
|
+
self.assertAlmostEqual(re102_limit_db(100e6, "C"), 44.0, places=6)
|
|
253
|
+
self.assertEqual(CE102_BAND_LO_HZ, 10e3)
|
|
254
|
+
self.assertEqual(CE102_BAND_HI_HZ, 10e6)
|
|
255
|
+
self.assertEqual(RE102_BAND_LO_HZ, 2e6)
|
|
256
|
+
self.assertEqual(RE102_BAND_HI_HZ, 18e9)
|
|
257
|
+
|
|
258
|
+
def test_erp_sanity_check(self):
|
|
259
|
+
# 100 W ERP at 10 m -> 5.477 V/m (~134.77 dBuV/m)
|
|
260
|
+
e = field_strength_from_erp(100.0, 10.0)
|
|
261
|
+
self.assertAlmostEqual(e, 5.477, places=2)
|
|
262
|
+
self.assertAlmostEqual(dbu_v_per_m_from_v_per_m(e), 134.77,
|
|
263
|
+
places=1)
|
|
264
|
+
|
|
265
|
+
def test_worst_case_and_verdict(self):
|
|
266
|
+
freqs = [50e3, 150e3, 5e6]
|
|
267
|
+
margins = [8.0, 5.0, 8.0]
|
|
268
|
+
wf, wm = worst_case_frequency(freqs, margins)
|
|
269
|
+
self.assertEqual(wf, 150e3)
|
|
270
|
+
self.assertAlmostEqual(wm, 5.0, places=6)
|
|
271
|
+
v = emission_verdict(margins, freqs, "A", "CE102")
|
|
272
|
+
self.assertTrue(v["pass"])
|
|
273
|
+
# negative worst margin fails
|
|
274
|
+
v2 = emission_verdict([-2.0], [100e6], "A", "RE102")
|
|
275
|
+
self.assertFalse(v2["pass"])
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class TestQualificationReportBuilder(unittest.TestCase):
|
|
279
|
+
|
|
280
|
+
def test_example_report_all_gates_pass(self):
|
|
281
|
+
model = build_qualification_report(example_item())
|
|
282
|
+
self.assertEqual(model["temperature_category"], "B1")
|
|
283
|
+
self.assertEqual(model["status"], "draft-for-review")
|
|
284
|
+
self.assertTrue(model["matrix_complete"])
|
|
285
|
+
self.assertEqual(model["esd"]["test_level_kv"], 15.0)
|
|
286
|
+
gates = check_qualification_report(model)
|
|
287
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
288
|
+
|
|
289
|
+
def test_report_sections_present(self):
|
|
290
|
+
md = example_qualification_report_markdown()
|
|
291
|
+
for sec in ["## 1. Scope", "## 3. Qualification test matrix",
|
|
292
|
+
"## 6. Power input (Section 16)",
|
|
293
|
+
"## 7. Radio frequency susceptibility (Section 20)",
|
|
294
|
+
"## 9. Verdicts and open items"]:
|
|
295
|
+
self.assertIn(sec, md)
|
|
296
|
+
self.assertIn("not an approval", md.lower())
|
|
297
|
+
|
|
298
|
+
def test_markdown_gates_all_pass(self):
|
|
299
|
+
md = example_qualification_report_markdown()
|
|
300
|
+
gates = check_qualification_report_markdown(md, "B1")
|
|
301
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
302
|
+
|
|
303
|
+
def test_standalone_no_skills_repo(self):
|
|
304
|
+
os.environ["AEROSKILLS_DEV"] = "/nonexistent"
|
|
305
|
+
model = build_qualification_report(example_item())
|
|
306
|
+
self.assertTrue(model["matrix_complete"])
|
|
307
|
+
md = render_qualification_report_markdown(model)
|
|
308
|
+
self.assertGreater(len(md), 2000)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == "__main__":
|
|
312
|
+
unittest.main()
|