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,92 @@
|
|
|
1
|
+
# Propulsion Design Report
|
|
2
|
+
|
|
3
|
+
**Item:** High-bypass turbofan for a 200-seat twin (design study)
|
|
4
|
+
**Vehicle / mission:** 200-seat twin-aisle-class transport, 2 wing-mounted engines - Subsonic transport cruise design point (M0.78, FL350)
|
|
5
|
+
**Engine class:** high-bypass turbofan (BPR 8, FPR 1.60, OPR 30, TIT 1650 K)
|
|
6
|
+
**Status:** draft-for-review
|
|
7
|
+
**Certification basis:** Design study in a FAR 33 engine-type context; no certification approval claimed
|
|
8
|
+
|
|
9
|
+
## 1. Requirement and selected cycle
|
|
10
|
+
|
|
11
|
+
- Thrust requirement (per engine, design point): 30.0 kN net at M0.78, 11 km ISA.
|
|
12
|
+
- Selected cycle: separate-stream turbofan with the fan on the low-pressure spool and the core compressor/turbine on the high-pressure spool. Rationale: high bypass ratio (8) lowers mean jet velocity and raises propulsive efficiency at a transonic cruise Mach; the OPR 30 / TIT 1650 K core delivers the cruise SFC of 0.541 lb/(lbf.h) computed below.
|
|
13
|
+
- Design airflow (total, matched to the requirement): 141.0 kg/s.
|
|
14
|
+
|
|
15
|
+
## 2. Cycle analysis (airbreathing)
|
|
16
|
+
|
|
17
|
+
On-design station state table (total temperature / total pressure, per kg/s inlet airflow basis; component efficiencies are stated inputs below):
|
|
18
|
+
|
|
19
|
+
| Station | Name | Tt (K) | Pt (kPa) |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| 2 | fan face | 245.4 | 35.5 |
|
|
22
|
+
| 13 | fan exit | 284.6 | 56.7 |
|
|
23
|
+
| 3 | compressor exit | 699.1 | 1063.7 |
|
|
24
|
+
| 4 | turbine inlet (TIT) | 1650.0 | 1010.5 |
|
|
25
|
+
| 5 | turbine exit | 996.2 | 97.5 |
|
|
26
|
+
|
|
27
|
+
Core nozzle (station 9): exit static 855 K / 52.7 kPa, jet velocity 565.6 m/s, Mach 1.000, choked, exit area 0.2936 m^2 (per engine).
|
|
28
|
+
Fan nozzle (station 19): exit static 237 K / 29.7 kPa, jet velocity 305.6 m/s, Mach 1.000, choked, exit area 1.1711 m^2 (per engine).
|
|
29
|
+
|
|
30
|
+
- Net thrust (per engine, design point): 30.0 kN = sum of m_dot*(V9 - V0) + (P9 - P0)*A9 over both streams.
|
|
31
|
+
- TSFC: 15.3 g/(kN.s) (0.541 lb/(lbf.h)).
|
|
32
|
+
- Fuel/air ratio (core): 0.02937 (total basis 0.00326); fuel flow 0.460 kg/s per engine.
|
|
33
|
+
- Efficiencies: overall 0.349, thermal 0.412, propulsive 0.848 (kinetic-energy method over both streams); ideal Brayton at OPR 30: 0.622.
|
|
34
|
+
|
|
35
|
+
Input assumptions (stated): fan efficiency 0.90, core compressor efficiency 0.90, turbine 0.90, combustor 0.995 (pressure ratio 0.95), inlet recovery 0.995, fan-duct ratio 0.99, mechanical 0.99, nozzle velocity coefficient 0.99; fuel LHV 43.2 MJ/kg (kerosene class). Component maps and stage counts are representative input.
|
|
36
|
+
|
|
37
|
+
## 3. Off-design
|
|
38
|
+
|
|
39
|
+
Quick off-design model (turbofan-off-design leaf): corrected mass flow and turbine inlet temperature held at the design value; physical airflow scales by delta/sqrt(theta); net thrust is recomputed through the on-design cycle at each flight point.
|
|
40
|
+
|
|
41
|
+
| Point | Alt (km) | Mach | Throttle | Verdict | m_dot (kg/s) | F_net (kN) | TSFC g/(kN.s) | Surge clearance | Map verdict |
|
|
42
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
43
|
+
| SLS static | 0 | 0.00 | 1.00 | max-continuous | 370.1 | 111.5 | 9.7 | 15% | on-map |
|
|
44
|
+
| FL250 climb | 8 | 0.78 | 1.00 | max-continuous | 213.0 | 39.4 | 16.7 | 15% | on-map |
|
|
45
|
+
| FL350 design cruise | 11 | 0.78 | 1.00 | max-continuous | 141.0 | 30.0 | 15.3 | 15% | on-map |
|
|
46
|
+
| FL350 part power | 11 | 0.78 | 0.60 | cruise | 141.0 | 18.0 | 15.3 | 15% | on-map |
|
|
47
|
+
| FL390 top of cruise | 12 | 0.80 | 1.00 | max-continuous | 119.0 | 25.7 | 15.1 | 15% | on-map |
|
|
48
|
+
|
|
49
|
+
Surge clearance: representative core-compressor surge line set 115% above the operating line pressure ratio (34 vs OPR 30); the operating line clears surge at every envelope point (representative map input, not vendor data).
|
|
50
|
+
|
|
51
|
+
## 4. Component selection
|
|
52
|
+
|
|
53
|
+
| Component | Choice | Basis |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| Inlet | subsonic pitot, recovery 0.995 | cruise M0.78 |
|
|
56
|
+
| Fan | single stage, PR 1.60, eta 0.90 | Tt 285 K at design |
|
|
57
|
+
| Core compressor | multi-stage axial, PR 18.8 (OPR 30/fan 1.60) | Tt 699 K, representative map margin |
|
|
58
|
+
| Combustor | annular, eta 0.995, pressure ratio 0.95 | f/a 0.02937 |
|
|
59
|
+
| HP turbine | cooled (turbine-blade-cooling stage), eta 0.90 | Tt in 1650 K, Tt out 996 K |
|
|
60
|
+
| Core nozzle | convergent, choked at design | area 0.2936 m^2 |
|
|
61
|
+
| Fan nozzle | convergent, choked at design | area 1.1711 m^2 |
|
|
62
|
+
|
|
63
|
+
## 5. Integration
|
|
64
|
+
|
|
65
|
+
- Ram-drag bookkeeping (engine-airframe integration): net thrust above already subtracts the inlet momentum drag m_dot_0*V0 (= 32.6 kN at the design point).
|
|
66
|
+
- Installed performance: nacelle, pylon and bleed/accessory losses are NOT yet deducted; the uninstalled net thrust is 30.0 kN per engine. Installation bookkeeping is a follow-on engine-airframe integration pass.
|
|
67
|
+
|
|
68
|
+
## 6. Rocket / space option (if applicable)
|
|
69
|
+
|
|
70
|
+
Not applicable to this airbreathing transport item - the engine selection is turbofan. For a space item the rocket-engine-cycle and nozzle-design stages apply (see the rocket reference example in the core / cli build --engine rocket).
|
|
71
|
+
|
|
72
|
+
## 7. Electric option (if applicable)
|
|
73
|
+
|
|
74
|
+
Not applicable: no electric-propulsion role for a 30 kN cruise thrust requirement at M0.78. Electric thrusters cover the low-thrust space regime (hall/gridded-ion), not airbreathing main propulsion.
|
|
75
|
+
|
|
76
|
+
## 8. Bypass-ratio trade and recommendation
|
|
77
|
+
|
|
78
|
+
First-order trade at fixed core conditions, fixed total mass flow and jet velocities held at the design-point values (bypass-ratio-trade leaf method):
|
|
79
|
+
|
|
80
|
+
| BPR | F/m_dot (N per kg/s) | TSFC g/(kN.s) | eta_o |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| 2 | 273.3 | 35.8 | 0.149 |
|
|
83
|
+
| 4 | 211.0 | 27.8 | 0.192 |
|
|
84
|
+
| 6 | 184.3 | 22.8 | 0.235 |
|
|
85
|
+
| 8 | 169.5 | 19.3 | 0.278 |
|
|
86
|
+
| 10 | 160.1 | 16.7 | 0.321 |
|
|
87
|
+
| 12 | 153.5 | 14.7 | 0.364 |
|
|
88
|
+
|
|
89
|
+
Raising BPR lowers specific thrust and TSFC by shifting flow to the fan stream (bypass-ratio-trade trend), at the cost of fan diameter and nacelle drag. BPR 8 balances specific thrust with nacelle integration for a 200-seat twin; the design point above is the recommendation. Open items: nacelle drag, turbine cooling flow, and the component map verification.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
*Generated by Aero Agent Roles propulsion-engineer core (2026-09-05). DRAFT for human propulsion lead review. Not an approval document.*
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Test propulsion_core: the executable engine of the Propulsion Engineer.
|
|
3
|
+
|
|
4
|
+
Proves the role can DO its job standalone (no AeroSkills needed): ISA,
|
|
5
|
+
Brayton/real-cycle relations, turbofan on-design thrust/TSFC/efficiency,
|
|
6
|
+
nozzle choked/unchoked logic, compressor-map surge verdicts, off-design
|
|
7
|
+
envelope, bypass-ratio trade, rocket Isp/mass-split/cycle feasibility,
|
|
8
|
+
report builder + evidence gates, and standalone mode.
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
16
|
+
"..", "core"))
|
|
17
|
+
from propulsion_core import ( # noqa: E402
|
|
18
|
+
PROPELLANTS, bpr_trend, build_report, build_rocket_report,
|
|
19
|
+
build_turbofan_report, brayton_thermal_efficiency, check_report,
|
|
20
|
+
check_report_markdown, compressor_exit_temperature,
|
|
21
|
+
cycle_feasibility, example_rocket_item, example_rocket_markdown,
|
|
22
|
+
example_turbofan_item, example_turbofan_markdown, exit_mach_from_area_ratio,
|
|
23
|
+
isa, map_verdict, mass_flow_split, off_design_envelope, operating_line_clearance,
|
|
24
|
+
real_thermal_efficiency, render_report_markdown, rocket_mass_flow,
|
|
25
|
+
rocket_nozzle_sizing, thrust_split, turbofan_design_point,
|
|
26
|
+
turbine_exit_temperature, turbine_power,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Deterministic across midnight: pin fresh renders to the date the
|
|
30
|
+
# committed template carries (CI override wins).
|
|
31
|
+
os.environ.setdefault("ROLE_GEN_DATE", "2026-09-05")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestCoreDomainRules(unittest.TestCase):
|
|
35
|
+
"""Public-standard anchors mirrored from the bound AeroSkills leaves."""
|
|
36
|
+
|
|
37
|
+
def test_isa_at_sea_level(self):
|
|
38
|
+
s = isa(0.0)
|
|
39
|
+
self.assertAlmostEqual(s["t"], 288.15, places=2)
|
|
40
|
+
self.assertAlmostEqual(s["p"], 101325.0, places=1)
|
|
41
|
+
|
|
42
|
+
def test_brayton_anchor_pr8(self):
|
|
43
|
+
# Ideal Brayton at PR 8, gamma 1.4: 0.44795 (leaf anchor).
|
|
44
|
+
self.assertAlmostEqual(brayton_thermal_efficiency(8.0), 0.44795,
|
|
45
|
+
delta=1e-3)
|
|
46
|
+
|
|
47
|
+
def test_compressor_anchor(self):
|
|
48
|
+
# T2 = T1*PR^((g-1)/g), 288 K / PR 8: 521.70 K (leaf anchor).
|
|
49
|
+
self.assertAlmostEqual(compressor_exit_temperature(288.0, 8.0),
|
|
50
|
+
521.70, delta=0.1)
|
|
51
|
+
# real-cycle with efficiency raises the exit temperature
|
|
52
|
+
self.assertGreater(
|
|
53
|
+
compressor_exit_temperature(288.0, 8.0, eta_c=0.85),
|
|
54
|
+
compressor_exit_temperature(288.0, 8.0, eta_c=1.0))
|
|
55
|
+
|
|
56
|
+
def test_turbine_anchor(self):
|
|
57
|
+
# T4 = T3/PR^((g-1)/g), 1400 K / PR 8: 772.86 K (leaf anchor).
|
|
58
|
+
self.assertAlmostEqual(turbine_exit_temperature(1400.0, 8.0, 1.4),
|
|
59
|
+
772.86, delta=0.1)
|
|
60
|
+
|
|
61
|
+
def test_real_thermal_efficiency_order(self):
|
|
62
|
+
# Requires physically ordered stations; net/heat < 1.
|
|
63
|
+
eta = real_thermal_efficiency(288.0, 600.0, 1500.0, 900.0)
|
|
64
|
+
self.assertGreater(eta, 0.0)
|
|
65
|
+
self.assertLess(eta, 1.0)
|
|
66
|
+
with self.assertRaises(ValueError):
|
|
67
|
+
real_thermal_efficiency(600.0, 288.0, 1500.0, 900.0)
|
|
68
|
+
|
|
69
|
+
def test_area_mach_supersonic(self):
|
|
70
|
+
# area ratio 2 -> Mach ~1.5-2 on the supersonic branch (gamma 1.2)
|
|
71
|
+
m = exit_mach_from_area_ratio(2.0, 1.2)
|
|
72
|
+
self.assertGreater(m, 1.0)
|
|
73
|
+
with self.assertRaises(ValueError):
|
|
74
|
+
exit_mach_from_area_ratio(1.0, 1.2) # sonic is not supersonic
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestTurbofanDesignPoint(unittest.TestCase):
|
|
78
|
+
"""On-design cycle: real numbers, physical ordering, gates."""
|
|
79
|
+
|
|
80
|
+
def test_cruise_design_point_realistic(self):
|
|
81
|
+
# Reference item: BPR 8 / OPR 30 / TIT 1650 at M0.78 FL350.
|
|
82
|
+
d = turbofan_design_point(0.78, 10668.0, 8.0, 1.6, 30.0, 1650.0)
|
|
83
|
+
# station temperature ordering
|
|
84
|
+
st = d["stations"]
|
|
85
|
+
self.assertLess(st["2"]["tt"], st["13"]["tt"])
|
|
86
|
+
self.assertLess(st["13"]["tt"], st["3"]["tt"])
|
|
87
|
+
self.assertLess(st["3"]["tt"], st["4"]["tt"])
|
|
88
|
+
self.assertLess(st["5"]["tt"], st["4"]["tt"])
|
|
89
|
+
# realistic compressor exit for OPR 30 at cruise (700 K ballpark)
|
|
90
|
+
self.assertTrue(600 < st["3"]["tt"] < 850, st["3"]["tt"])
|
|
91
|
+
# net thrust per kg/s air positive and in a physical band (N)
|
|
92
|
+
self.assertTrue(150 < d["f_net_per_kg"] < 250, d["f_net_per_kg"])
|
|
93
|
+
# cruise TSFC in the modern high-bypass band: 12-20 g/(kN s)
|
|
94
|
+
tsfc = d["tsfc_kg_Ns"] * 1e6
|
|
95
|
+
self.assertTrue(12.0 < tsfc < 20.0, tsfc)
|
|
96
|
+
# efficiencies bounded and consistent: eta_o = eta_th * eta_p
|
|
97
|
+
self.assertLess(d["eta_o"], 1.0)
|
|
98
|
+
self.assertLess(d["eta_p"], 1.0)
|
|
99
|
+
self.assertAlmostEqual(d["eta_o"], d["eta_th"] * d["eta_p"], delta=1e-6)
|
|
100
|
+
|
|
101
|
+
def test_scaled_to_thrust_requirement(self):
|
|
102
|
+
item = example_turbofan_item()
|
|
103
|
+
model = build_turbofan_report(item)
|
|
104
|
+
d = model["design"]
|
|
105
|
+
# sizing hits the requirement
|
|
106
|
+
self.assertAlmostEqual(d["f_net"], 30000.0, delta=100.0)
|
|
107
|
+
self.assertGreater(d["mdot0"], 50.0) # 100+ kg/s class airflow
|
|
108
|
+
self.assertGreater(d["mdot_fuel"], 0.3) # kg/s fuel flow
|
|
109
|
+
|
|
110
|
+
def test_sls_static_nozzles_unchoked(self):
|
|
111
|
+
# at SLS (M0, sea level) the nozzle pressure ratios are below the
|
|
112
|
+
# critical ratio (fan NPR ~1.6 < 1.89), so both are unchoked and
|
|
113
|
+
# fully expanded to ambient.
|
|
114
|
+
d = turbofan_design_point(0.0, 0.0, 8.0, 1.6, 30.0, 1650.0)
|
|
115
|
+
self.assertFalse(d["nozzle_core"]["choked"])
|
|
116
|
+
self.assertFalse(d["nozzle_fan"]["choked"])
|
|
117
|
+
self.assertEqual(d["v0"], 0.0)
|
|
118
|
+
# at the M0.78 / FL350 design point both nozzles are choked
|
|
119
|
+
c = turbofan_design_point(0.78, 10668.0, 8.0, 1.6, 30.0, 1650.0)
|
|
120
|
+
self.assertTrue(c["nozzle_core"]["choked"])
|
|
121
|
+
self.assertTrue(c["nozzle_fan"]["choked"])
|
|
122
|
+
|
|
123
|
+
def test_bad_inputs_raise(self):
|
|
124
|
+
with self.assertRaises(ValueError):
|
|
125
|
+
turbofan_design_point(1.2, 0.0, 8.0, 1.6, 30.0, 1650.0)
|
|
126
|
+
with self.assertRaises(ValueError):
|
|
127
|
+
turbofan_design_point(0.78, 10668.0, 0.0, 1.6, 30.0, 1650.0)
|
|
128
|
+
with self.assertRaises(ValueError):
|
|
129
|
+
turbofan_design_point(0.78, 10668.0, 8.0, 1.0, 30.0, 1650.0)
|
|
130
|
+
with self.assertRaises(ValueError):
|
|
131
|
+
turbofan_design_point(0.78, 10668.0, 8.0, 1.6, 30.0, 1400.0)
|
|
132
|
+
|
|
133
|
+
def test_thrust_identity(self):
|
|
134
|
+
# momentum+pressure form reproduces thrust: F = mdot(V9-V0)+(P9-P0)A9
|
|
135
|
+
d = turbofan_design_point(0.78, 10668.0, 8.0, 1.6, 30.0, 1650.0)
|
|
136
|
+
nc, nf = d["nozzle_core"], d["nozzle_fan"]
|
|
137
|
+
f = (d["mdot_core_frac"] * (1 + d["f_air_core"]) * (nc["ve"] - d["v0"])
|
|
138
|
+
+ (nc["pe"] - d["p0"]) * nc["a_exit"]
|
|
139
|
+
+ d["mdot_fan_frac"] * (nf["ve"] - d["v0"])
|
|
140
|
+
+ (nf["pe"] - d["p0"]) * nf["a_exit"])
|
|
141
|
+
self.assertAlmostEqual(f, d["f_net_per_kg"], delta=1e-6)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TestOffDesignAndTrade(unittest.TestCase):
|
|
145
|
+
def test_off_design_envelope_covers_points(self):
|
|
146
|
+
d = turbofan_design_point(0.78, 10668.0, 8.0, 1.6, 30.0, 1650.0)
|
|
147
|
+
d["mdot0"] = 100.0
|
|
148
|
+
pts = [("SLS", 0.0, 0.0, 1.0), ("FL250", 7620.0, 0.78, 1.0),
|
|
149
|
+
("part power", 10668.0, 0.78, 0.6)]
|
|
150
|
+
rows = off_design_envelope(d, pts)
|
|
151
|
+
self.assertEqual(len(rows), 3)
|
|
152
|
+
for r in rows:
|
|
153
|
+
self.assertGreater(r["mdot_phys"], 0.0)
|
|
154
|
+
self.assertGreater(r["f_net"], 0.0)
|
|
155
|
+
self.assertIn(r["map_verdict"], ("on-map", "approaching-surge"))
|
|
156
|
+
self.assertTrue(r["surge_clearance_pct"] > 0.0)
|
|
157
|
+
# named point carried through, part-power below the max row
|
|
158
|
+
self.assertEqual(rows[0]["name"], "SLS")
|
|
159
|
+
self.assertLess(rows[2]["f_throttled"], rows[2]["f_net"])
|
|
160
|
+
|
|
161
|
+
def test_surge_clearance_and_verdict(self):
|
|
162
|
+
# clearance positive below surge; verdict on-map until within 5%
|
|
163
|
+
self.assertAlmostEqual(operating_line_clearance(20.0, 23.0), 15.0)
|
|
164
|
+
self.assertEqual(map_verdict(20.0, 23.0), "on-map")
|
|
165
|
+
self.assertEqual(map_verdict(22.4, 23.0), "approaching-surge")
|
|
166
|
+
with self.assertRaises(ValueError):
|
|
167
|
+
map_verdict(23.0, 23.0) # on/above the surge line is rejected
|
|
168
|
+
|
|
169
|
+
def test_thrust_split_and_bpr_trend(self):
|
|
170
|
+
s = thrust_split(8.0, 90.0, 500.0, 300.0, 231.0)
|
|
171
|
+
self.assertGreater(s["f_total"], 0.0)
|
|
172
|
+
# trend signature: bpr_trend(mdot_total, vj_core, vj_fan, v0, f_core)
|
|
173
|
+
trend = bpr_trend(90.0, 500.0, 300.0, 231.0, 0.02)
|
|
174
|
+
self.assertEqual(len(trend), 6)
|
|
175
|
+
# fixed core/fan jet velocities, fixed total flow (leaf model):
|
|
176
|
+
# higher BPR shifts flow to the slower fan stream -> specific
|
|
177
|
+
# thrust and TSFC both fall monotonically.
|
|
178
|
+
specs = [r["specific_thrust"] for r in trend]
|
|
179
|
+
tsfcs = [r["tsfc_g_kN_s"] for r in trend]
|
|
180
|
+
self.assertEqual(specs, sorted(specs, reverse=True))
|
|
181
|
+
self.assertEqual(tsfcs, sorted(tsfcs, reverse=True))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class TestRocketDomain(unittest.TestCase):
|
|
185
|
+
def test_propellant_table(self):
|
|
186
|
+
# reference-typical values from the rocket-engine-cycle leaf
|
|
187
|
+
self.assertEqual(PROPELLANTS["LOX/LH2"], (1140.0, 71.0, 5.5, 430.0))
|
|
188
|
+
self.assertEqual(PROPELLANTS["LOX/RP-1"], (1140.0, 820.0, 2.56, 300.0))
|
|
189
|
+
|
|
190
|
+
def test_mass_flow_from_isp(self):
|
|
191
|
+
# mdot = F/(Isp*g0): 110 kN at Isp 430 -> ~26.1 kg/s
|
|
192
|
+
m = rocket_mass_flow(110000.0, 430.0)
|
|
193
|
+
self.assertAlmostEqual(m, 26.1, delta=0.2)
|
|
194
|
+
mdot, mox, mf = mass_flow_split(110000.0, 430.0, 5.5)
|
|
195
|
+
self.assertAlmostEqual(mdot, mox + mf, delta=1e-9)
|
|
196
|
+
self.assertAlmostEqual(mox / mf, 5.5, delta=1e-9)
|
|
197
|
+
|
|
198
|
+
def test_cycle_feasibility_bounds(self):
|
|
199
|
+
# pressure-fed bound at 3 MPa; expander LOX/LH2 <= 10 MPa
|
|
200
|
+
ok, _ = cycle_feasibility("pressure-fed", "LOX/LH2", 2.0e6)
|
|
201
|
+
self.assertTrue(ok)
|
|
202
|
+
ok, _ = cycle_feasibility("pressure-fed", "LOX/LH2", 4.0e6)
|
|
203
|
+
self.assertFalse(ok)
|
|
204
|
+
ok, _ = cycle_feasibility("expander", "LOX/LH2", 8.0e6)
|
|
205
|
+
self.assertTrue(ok)
|
|
206
|
+
ok, _ = cycle_feasibility("expander", "LOX/RP-1", 4.0e6)
|
|
207
|
+
self.assertFalse(ok) # expander needs the LH2 fuel
|
|
208
|
+
ok, _ = cycle_feasibility("staged-combustion", "LOX/RP-1", 20.0e6)
|
|
209
|
+
self.assertTrue(ok)
|
|
210
|
+
|
|
211
|
+
def test_turbine_power_positive(self):
|
|
212
|
+
p = turbine_power(4.0, 2000.0, 1200.0, 0.6, 1.2, 3.2e6, 0.2e6)
|
|
213
|
+
self.assertGreater(p, 0.0)
|
|
214
|
+
|
|
215
|
+
def test_nozzle_sizing(self):
|
|
216
|
+
n = rocket_nozzle_sizing(26.1, 4.0e6, 3500.0, 1.2, 84.0, r_gas=693.0)
|
|
217
|
+
self.assertGreater(n["a_star"], 0.0)
|
|
218
|
+
self.assertGreater(n["me"], 3.0) # supersonic, high area ratio
|
|
219
|
+
self.assertLess(n["pe"], 4.0e6)
|
|
220
|
+
self.assertGreater(n["cf_ideal"], 1.5) # vacuum Cf of a good nozzle
|
|
221
|
+
# ideal exit velocity implies ideal vacuum Isp > real typical
|
|
222
|
+
self.assertGreater(n["ve"] / 9.80665, 430.0)
|
|
223
|
+
|
|
224
|
+
def test_rocket_report_builder(self):
|
|
225
|
+
model = build_rocket_report(example_rocket_item())
|
|
226
|
+
self.assertEqual(model["engine_kind"], "rocket")
|
|
227
|
+
self.assertEqual(model["cycle"]["propellant"], "LOX/LH2")
|
|
228
|
+
self.assertTrue(model["cycle"]["feasible"])
|
|
229
|
+
gates = check_report(model)
|
|
230
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
231
|
+
md = render_report_markdown(model)
|
|
232
|
+
self.assertIn("not an approval", md.lower())
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestReportBuilderAndGates(unittest.TestCase):
|
|
236
|
+
def test_turbofan_report_gates(self):
|
|
237
|
+
model = build_report(example_turbofan_item())
|
|
238
|
+
self.assertEqual(model["document_type"], "Propulsion Design Report")
|
|
239
|
+
self.assertEqual(model["status"], "draft-for-review")
|
|
240
|
+
gates = check_report(model)
|
|
241
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
242
|
+
|
|
243
|
+
def test_markdown_gates(self):
|
|
244
|
+
md = example_turbofan_markdown()
|
|
245
|
+
gates = check_report_markdown(md)
|
|
246
|
+
self.assertTrue(gates["all_pass"], gates)
|
|
247
|
+
for section in ("## 1. Requirement", "## 2. Cycle analysis",
|
|
248
|
+
"## 3. Off-design", "## 4. Component selection",
|
|
249
|
+
"## 8. Bypass-ratio trade"):
|
|
250
|
+
self.assertIn(section, md)
|
|
251
|
+
self.assertIn("not an approval", md.lower())
|
|
252
|
+
|
|
253
|
+
def test_report_has_computed_numbers(self):
|
|
254
|
+
# evidence gate: every headline performance claim is a number
|
|
255
|
+
md = example_turbofan_markdown()
|
|
256
|
+
self.assertRegex(md, r"kN")
|
|
257
|
+
self.assertRegex(md, r"g/\(kN\.s\)|lb/\(lbf\.h\)")
|
|
258
|
+
self.assertRegex(md, r"\d+\.\d+ kg/s")
|
|
259
|
+
self.assertRegex(md, r"K")
|
|
260
|
+
|
|
261
|
+
def test_rocket_markdown_no_blank_template(self):
|
|
262
|
+
md = example_rocket_markdown()
|
|
263
|
+
self.assertNotIn("___", md)
|
|
264
|
+
self.assertIn("not an approval", md.lower())
|
|
265
|
+
|
|
266
|
+
def test_deliverable_template_filled(self):
|
|
267
|
+
# templates/propulsion-report-template.md is the FILLED example
|
|
268
|
+
# deliverable (the reference turbofan item): zero blanks, and it
|
|
269
|
+
# matches what the core renders today (modulo the date footer).
|
|
270
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
271
|
+
tmpl = os.path.join(here, "..", "templates",
|
|
272
|
+
"propulsion-report-template.md")
|
|
273
|
+
with open(tmpl) as f:
|
|
274
|
+
text = f.read()
|
|
275
|
+
self.assertNotIn("___", text)
|
|
276
|
+
low = text.lower()
|
|
277
|
+
self.assertIn("not an approval", low)
|
|
278
|
+
self.assertIn("draft", low)
|
|
279
|
+
fresh = example_turbofan_markdown()
|
|
280
|
+
norm = lambda s: re.sub(r"\(20\d\d-\d\d-\d\d\)", "(DATE)", s)
|
|
281
|
+
self.assertEqual(norm(text), norm(fresh))
|
|
282
|
+
|
|
283
|
+
def test_standalone_no_skills_repo(self):
|
|
284
|
+
os.environ["AEROSKILLS_DEV"] = "/nonexistent"
|
|
285
|
+
for item, render in ((example_turbofan_item(), example_turbofan_markdown),
|
|
286
|
+
(example_rocket_item(), example_rocket_markdown)):
|
|
287
|
+
model = build_report(item)
|
|
288
|
+
self.assertTrue(check_report(model)["all_pass"])
|
|
289
|
+
md = render()
|
|
290
|
+
self.assertGreater(len(md), 1000)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
if __name__ == "__main__":
|
|
294
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Role test: propulsion-engineer."""
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
ROLES_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
|
|
8
|
+
os.path.abspath(__file__)))))
|
|
9
|
+
AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
|
|
10
|
+
HAS_SKILLS = os.path.isdir(os.path.join(AEROSKILLS, "skills"))
|
|
11
|
+
ROLE_DIR = os.path.join(ROLES_REPO, "roles", "propulsion-engineer")
|
|
12
|
+
TEMPLATE = os.path.join(ROLE_DIR, "templates", "propulsion-report-template.md")
|
|
13
|
+
|
|
14
|
+
EXPECTED_BOUND = [
|
|
15
|
+
"propulsion/gas-turbine-cycle/gas-turbine-cycle",
|
|
16
|
+
"propulsion/gas-turbine-cycle/real-cycle-effects",
|
|
17
|
+
"propulsion/gas-turbine-cycle/afterburner-cycle",
|
|
18
|
+
"propulsion/gas-turbine-cycle/regenerative-cycle",
|
|
19
|
+
"propulsion/gas-turbine-cycle/propelling-nozzle",
|
|
20
|
+
"propulsion/gas-turbine-cycle/subsonic-inlet-recovery",
|
|
21
|
+
"propulsion/gas-turbine-cycle/combustor-design",
|
|
22
|
+
"propulsion/turbofan/turbofan-cycle",
|
|
23
|
+
"propulsion/turbofan/bypass-ratio-trade",
|
|
24
|
+
"propulsion/turbofan/turbofan-off-design",
|
|
25
|
+
"propulsion/turboprop/turboprop-cycle",
|
|
26
|
+
"propulsion/turboprop/free-turbine",
|
|
27
|
+
"propulsion/axial-compressor/axial-compressor-stage",
|
|
28
|
+
"propulsion/axial-compressor/compressor-map",
|
|
29
|
+
"propulsion/axial-compressor/multi-stage-compressor",
|
|
30
|
+
"propulsion/axial-compressor/turbine-stage",
|
|
31
|
+
"propulsion/axial-compressor/turbine-blade-cooling",
|
|
32
|
+
"propulsion/engine-airframe/engine-airframe-integration",
|
|
33
|
+
"propulsion/rocket/rocket-engine-cycle",
|
|
34
|
+
"propulsion/rocket/nozzle-design",
|
|
35
|
+
"propulsion/rocket/combustion-chamber-design",
|
|
36
|
+
"propulsion/rocket/injector-design",
|
|
37
|
+
"propulsion/rocket/propellant-selection",
|
|
38
|
+
"propulsion/rocket/solid-rocket-motor",
|
|
39
|
+
"propulsion/rocket/hybrid-rocket-motor",
|
|
40
|
+
"propulsion/rocket/thrust-chamber-cooling",
|
|
41
|
+
"propulsion/rocket/thrust-vector-control",
|
|
42
|
+
"propulsion/rocket/cold-gas-thruster",
|
|
43
|
+
"propulsion/combustion/cea-rocket-combustion",
|
|
44
|
+
"propulsion/electric/hall-thruster",
|
|
45
|
+
"propulsion/electric/gridded-ion-thruster",
|
|
46
|
+
"propulsion/electric/electrothermal-thruster",
|
|
47
|
+
"propulsion/ramjet/ramjet-cycle",
|
|
48
|
+
"propulsion/ramjet/ramjet-inlet"
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
STAGES = [
|
|
52
|
+
"1. Airbreathing cycle",
|
|
53
|
+
"2. Bypass/regenerative/AB trades",
|
|
54
|
+
"3. Off-design",
|
|
55
|
+
"4. Components",
|
|
56
|
+
"5. Turbine cooling",
|
|
57
|
+
"6. Integration",
|
|
58
|
+
"7. Rocket cycle",
|
|
59
|
+
"8. Solid/hybrid",
|
|
60
|
+
"9. Cooling/TVC",
|
|
61
|
+
"10. Combustion",
|
|
62
|
+
"11. Electric",
|
|
63
|
+
"12. Ramjet",
|
|
64
|
+
"13. Report"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def role_text():
|
|
69
|
+
p = os.path.join(ROLE_DIR, "ROLE.md")
|
|
70
|
+
return open(p).read() if os.path.exists(p) else None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestPropulsionEngineerRole(unittest.TestCase):
|
|
74
|
+
|
|
75
|
+
def test_bound_skills_resolve(self):
|
|
76
|
+
if not HAS_SKILLS:
|
|
77
|
+
self.skipTest("skills checkout absent (cross-repo dev check)")
|
|
78
|
+
for leaf in EXPECTED_BOUND:
|
|
79
|
+
self.assertTrue(
|
|
80
|
+
os.path.exists(os.path.join(AEROSKILLS, "skills", leaf, "SKILL.md")),
|
|
81
|
+
f"bound skill missing: {leaf}")
|
|
82
|
+
|
|
83
|
+
def test_stages_ordered(self):
|
|
84
|
+
role = role_text()
|
|
85
|
+
self.assertIsNotNone(role)
|
|
86
|
+
wf = role[role.find("## Workflow"):role.find("## Evidence gates")]
|
|
87
|
+
idx = [wf.find(s) for s in STAGES]
|
|
88
|
+
self.assertTrue(all(i >= 0 for i in idx), "stage missing in workflow")
|
|
89
|
+
self.assertEqual(idx, sorted(idx), "stage order not deterministic")
|
|
90
|
+
|
|
91
|
+
def test_template_present(self):
|
|
92
|
+
self.assertTrue(os.path.exists(TEMPLATE), "deliverable template missing")
|
|
93
|
+
|
|
94
|
+
def test_boundaries(self):
|
|
95
|
+
role = role_text()
|
|
96
|
+
self.assertIsNotNone(role)
|
|
97
|
+
for phrase in ["sign_off_required: true", "forbidden"]:
|
|
98
|
+
self.assertIn(phrase, role)
|
|
99
|
+
|
|
100
|
+
def test_sources(self):
|
|
101
|
+
self.assertTrue(os.path.exists(os.path.join(ROLE_DIR, "SOURCES.md")),
|
|
102
|
+
"SOURCES.md missing")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
unittest.main()
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: role
|
|
3
|
+
name: space-systems-engineer
|
|
4
|
+
title: "Space Systems Engineer"
|
|
5
|
+
status: draft
|
|
6
|
+
domain: space-systems
|
|
7
|
+
deliverable_type: "spacecraft mission + subsystem design report"
|
|
8
|
+
standards_bound:
|
|
9
|
+
- id: ecss
|
|
10
|
+
tier: TIER-2
|
|
11
|
+
reference-only: true
|
|
12
|
+
skills_bound:
|
|
13
|
+
- space-systems/mission-design/mission-delta-v-budget
|
|
14
|
+
- space-systems/mission-design/launch-window-analysis
|
|
15
|
+
- space-systems/mission-design/c3-departure-energy
|
|
16
|
+
- space-systems/mission-design/entry-descent-landing
|
|
17
|
+
- space-systems/mission-design/radiation-debris
|
|
18
|
+
- space-systems/orbit-mechanics/keplerian-elements
|
|
19
|
+
- space-systems/orbit-mechanics/kepler-orbit-propagation
|
|
20
|
+
- space-systems/orbit-mechanics/hohmann-transfer
|
|
21
|
+
- space-systems/orbit-mechanics/bi-elliptic-transfer
|
|
22
|
+
- space-systems/orbit-mechanics/plane-change-maneuver
|
|
23
|
+
- space-systems/orbit-mechanics/lambert-transfer
|
|
24
|
+
- space-systems/orbit-mechanics/gravity-assist-swingby
|
|
25
|
+
- space-systems/orbit-mechanics/low-thrust-spiral
|
|
26
|
+
- space-systems/orbit-mechanics/orbital-perturbations
|
|
27
|
+
- space-systems/orbit-mechanics/sun-synchronous-inclination
|
|
28
|
+
- space-systems/orbit-mechanics/ground-track-repeat
|
|
29
|
+
- space-systems/orbit-mechanics/eclipse-time
|
|
30
|
+
- space-systems/orbit-mechanics/satellite-coverage
|
|
31
|
+
- space-systems/orbit-mechanics/walker-delta-constellation
|
|
32
|
+
- space-systems/orbit-mechanics/geostationary-station-keeping
|
|
33
|
+
- space-systems/orbit-mechanics/conjunction-assessment
|
|
34
|
+
- space-systems/orbit-mechanics/three-body-libration
|
|
35
|
+
- space-systems/orbit-mechanics/orbital-decay
|
|
36
|
+
- space-systems/orbit-mechanics/clohessy-wiltshire
|
|
37
|
+
- space-systems/adcs/attitude-determination-triad
|
|
38
|
+
- space-systems/adcs/attitude-determination-quest
|
|
39
|
+
- space-systems/adcs/star-tracker
|
|
40
|
+
- space-systems/adcs/sun-pointing
|
|
41
|
+
- space-systems/adcs/reaction-wheel-control
|
|
42
|
+
- space-systems/adcs/control-moment-gyro
|
|
43
|
+
- space-systems/adcs/magnetorquer-control
|
|
44
|
+
- space-systems/adcs/attitude-control-sizing
|
|
45
|
+
- space-systems/adcs/pointing-error-budget
|
|
46
|
+
- space-systems/adcs/gyro-allan-variance
|
|
47
|
+
- space-systems/subsystems/communication-link-budget
|
|
48
|
+
- space-systems/subsystems/antenna-aperture-sizing
|
|
49
|
+
- space-systems/subsystems/power-thermal-budget
|
|
50
|
+
- space-systems/subsystems/solar-array-sizing
|
|
51
|
+
- space-systems/subsystems/spacecraft-battery-sizing
|
|
52
|
+
- space-systems/subsystems/thermal-design
|
|
53
|
+
- space-systems/subsystems/propellant-tank-sizing
|
|
54
|
+
- space-systems/subsystems/command-data-handling
|
|
55
|
+
- space-systems/ecss/systems-engineering
|
|
56
|
+
- space-systems/ecss/software-engineering
|
|
57
|
+
- space-systems/ecss/software-verification
|
|
58
|
+
tools_allowed: [stdlib, offline-file-processing]
|
|
59
|
+
forbidden:
|
|
60
|
+
- "claim certification approval"
|
|
61
|
+
- "declare launch readiness"
|
|
62
|
+
- "reproduce proprietary standard text"
|
|
63
|
+
sign_off_required: true
|
|
64
|
+
license: Apache-2.0
|
|
65
|
+
compatibility: "agentskills.io ROLE.md; any SKILL.md host"
|
|
66
|
+
metadata:
|
|
67
|
+
version: 0.1.0
|
|
68
|
+
author: Aero Agent Roles
|
|
69
|
+
skills_release: "v1.3.0+"
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
# Space Systems Engineer
|
|
73
|
+
|
|
74
|
+
## Role identity
|
|
75
|
+
|
|
76
|
+
Owns spacecraft mission and subsystem design: delta-v budget, transfer
|
|
77
|
+
and maneuver design, constellation/coverage analysis, ADCS selection,
|
|
78
|
+
and the subsystem sizing loop (power, thermal, comms, propulsion,
|
|
79
|
+
C&DH) that closes a satellite design against its mission. Use when a
|
|
80
|
+
space mission needs a defensible spacecraft with a delta-v budget,
|
|
81
|
+
orbit, attitude control solution, and subsystem power/thermal/comm
|
|
82
|
+
budgets. Do NOT use for aircraft certification or airframe roles.
|
|
83
|
+
|
|
84
|
+
## Deliverable contract
|
|
85
|
+
|
|
86
|
+
1. **Mission design report** - orbit + maneuver plan, delta-v budget,
|
|
87
|
+
launch window, coverage/constellation result, radiation/debris notes.
|
|
88
|
+
2. **Subsystem design report** - ADCS selection + pointing budget,
|
|
89
|
+
power/thermal/comm budgets, solar array/battery/tank sizing, C&DH.
|
|
90
|
+
3. **System budget summary** - mass, power, delta-v, pointing.
|
|
91
|
+
|
|
92
|
+
## Workflow (stages -> bound skills)
|
|
93
|
+
|
|
94
|
+
| Stage | Loads skill | Produces |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| 1. Mission delta-v | mission-delta-v-budget + keplerian-elements + kepler-orbit-propagation | orbit elements, total delta-v |
|
|
97
|
+
| 2. Transfers | hohmann-transfer + bi-elliptic-transfer + plane-change-maneuver + lambert-transfer + gravity-assist-swingby + low-thrust-spiral | transfer solution |
|
|
98
|
+
| 3. Launch window | launch-window-analysis + c3-departure-energy | window/C3 |
|
|
99
|
+
| 4. Constellation/coverage | satellite-coverage + walker-delta-constellation + ground-track-repeat + sun-synchronous-inclination + geostationary-station-keeping | coverage result |
|
|
100
|
+
| 5. Environment | eclipse-time + orbital-perturbations + conjunction-assessment + radiation-debris + orbital-decay + three-body-libration | environment inputs |
|
|
101
|
+
| 6. Rendezvous | clohessy-wiltshire | proximity solution |
|
|
102
|
+
| 7. ADCS determination | attitude-determination-triad + attitude-determination-quest + star-tracker + sun-pointing + gyro-allan-variance | attitude knowledge |
|
|
103
|
+
| 8. ADCS control | reaction-wheel-control + control-moment-gyro + magnetorquer-control + attitude-control-sizing + pointing-error-budget | ADCS solution + pointing |
|
|
104
|
+
| 9. Power/thermal | power-thermal-budget + solar-array-sizing + spacecraft-battery-sizing + thermal-design | power/thermal budgets |
|
|
105
|
+
| 10. Comms | communication-link-budget + antenna-aperture-sizing | link budget |
|
|
106
|
+
| 11. Propulsion/C&DH | propellant-tank-sizing + command-data-handling | propulsion sizing, C&DH |
|
|
107
|
+
| 12. ECSS context | ecss/systems-engineering + ecss/software-engineering + ecss/software-verification | process + software context |
|
|
108
|
+
| 13. Report | (all above) | mission + subsystem reports |
|
|
109
|
+
|
|
110
|
+
## Evidence gates
|
|
111
|
+
|
|
112
|
+
- Stage 1 done = delta-v budget sums with margins; elements consistent
|
|
113
|
+
with the mission.
|
|
114
|
+
- Stage 8 done = pointing error budget meets the requirement.
|
|
115
|
+
- Stage 9 done = power budget closes with eclipse sizing; thermal
|
|
116
|
+
balance stated.
|
|
117
|
+
- FINAL = every budget closes with a stated margin policy.
|
|
118
|
+
|
|
119
|
+
## Boundary / forbidden
|
|
120
|
+
|
|
121
|
+
- NEVER claim certification approval or declare launch readiness.
|
|
122
|
+
- ECSS is referenced (TIER-2), never reproduced.
|
|
123
|
+
- Subsystem performance assumes the stated models and margins.
|
|
124
|
+
|
|
125
|
+
## Verification
|
|
126
|
+
|
|
127
|
+
The role runs STANDALONE: `core/space_systems_core.py` is an executable
|
|
128
|
+
engine that computes the orbit elements, delta-v budget (Hohmann and
|
|
129
|
+
disposal legs), propellant mass (rocket equation), reaction wheel sizing
|
|
130
|
+
(h = J*omega), pointing error budget, battery and solar array sizing,
|
|
131
|
+
radiator area, and the comms link budget, then BUILDS the mission +
|
|
132
|
+
subsystem design report and gate-checks deliverables. No AeroSkills
|
|
133
|
+
checkout required.
|
|
134
|
+
|
|
135
|
+
Run the role:
|
|
136
|
+
```bash
|
|
137
|
+
python3 cli.py build --out report.md # example mission
|
|
138
|
+
python3 cli.py build --out leo550.md --altitude 550 # variant orbit
|
|
139
|
+
python3 cli.py build --out heavy.md --dry-mass 400 --isp 250
|
|
140
|
+
python3 cli.py check --file report.md # gate-check a report
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Tests:
|
|
144
|
+
- tests/test_space_systems_engineer_core.py (28 tests): domain rules
|
|
145
|
+
with AeroSkills leaf anchor values + report builder + gates +
|
|
146
|
+
standalone (no skills repo).
|
|
147
|
+
- tests/test_role_space_systems_engineer.py: bound-skill resolution
|
|
148
|
+
(skips if the skills repo is absent) + workflow + template present +
|
|
149
|
+
boundaries.
|
|
150
|
+
|
|
151
|
+
Bound skills in Aero Skills deepen individual stages when the library
|
|
152
|
+
is present (kepler propagation, transfer trade-offs, pointing error
|
|
153
|
+
refinement, detailed tank design); the core engine does not depend on
|
|
154
|
+
them. The worked example in `templates/space-systems-template.md` is the
|
|
155
|
+
generated output of the core for the example mission - REAL numbers, no
|
|
156
|
+
blanks.
|
|
157
|
+
|
|
158
|
+
## Compliance
|
|
159
|
+
|
|
160
|
+
- ecss TIER-2 reference-only. References summary-not-copy.
|
|
161
|
+
- SOURCES.md records standards referenced.
|