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.
Files changed (170) hide show
  1. package/NOTICE +18 -0
  2. package/README.md +49 -0
  3. package/bin/aero-agent-roles.js +127 -0
  4. package/lib/catalog.js +97 -0
  5. package/lib/install.js +48 -0
  6. package/lib/mcp.js +199 -0
  7. package/manifest.json +516 -0
  8. package/package.json +44 -0
  9. package/roles/aerodynamics-engineer/ROLE.md +153 -0
  10. package/roles/aerodynamics-engineer/SOURCES.md +12 -0
  11. package/roles/aerodynamics-engineer/cli.py +143 -0
  12. package/roles/aerodynamics-engineer/core/aerodynamics_core.py +1218 -0
  13. package/roles/aerodynamics-engineer/templates/aero-design-report-template.md +72 -0
  14. package/roles/aerodynamics-engineer/tests/test_aerodynamics_core.py +227 -0
  15. package/roles/aerodynamics-engineer/tests/test_role_aerodynamics.py +94 -0
  16. package/roles/aircraft-design-engineer/ROLE.md +138 -0
  17. package/roles/aircraft-design-engineer/SOURCES.md +12 -0
  18. package/roles/aircraft-design-engineer/cli.py +152 -0
  19. package/roles/aircraft-design-engineer/core/aircraft_design_core.py +1278 -0
  20. package/roles/aircraft-design-engineer/templates/concept-design-template.md +99 -0
  21. package/roles/aircraft-design-engineer/tests/test_aircraft_design_core.py +269 -0
  22. package/roles/aircraft-design-engineer/tests/test_role_aircraft_design_engineer.py +106 -0
  23. package/roles/airworthiness-compliance-engineer/ROLE.md +122 -0
  24. package/roles/airworthiness-compliance-engineer/SOURCES.md +16 -0
  25. package/roles/airworthiness-compliance-engineer/cli.py +146 -0
  26. package/roles/airworthiness-compliance-engineer/core/airworthiness_core.py +894 -0
  27. package/roles/airworthiness-compliance-engineer/templates/compliance-matrix-template.md +46 -0
  28. package/roles/airworthiness-compliance-engineer/tests/test_airworthiness_core.py +224 -0
  29. package/roles/airworthiness-compliance-engineer/tests/test_role_airworthiness.py +85 -0
  30. package/roles/as9100-quality-auditor/ROLE.md +128 -0
  31. package/roles/as9100-quality-auditor/SOURCES.md +16 -0
  32. package/roles/as9100-quality-auditor/cli.py +136 -0
  33. package/roles/as9100-quality-auditor/core/as9100_core.py +857 -0
  34. package/roles/as9100-quality-auditor/templates/findings-report-template.md +119 -0
  35. package/roles/as9100-quality-auditor/tests/test_as9100_cli_profile.py +105 -0
  36. package/roles/as9100-quality-auditor/tests/test_as9100_core.py +204 -0
  37. package/roles/as9100-quality-auditor/tests/test_role_as9100.py +90 -0
  38. package/roles/data-bus-avionics-engineer/ROLE.md +146 -0
  39. package/roles/data-bus-avionics-engineer/SOURCES.md +23 -0
  40. package/roles/data-bus-avionics-engineer/cli.py +311 -0
  41. package/roles/data-bus-avionics-engineer/core/data_bus_avionics_core.py +1121 -0
  42. package/roles/data-bus-avionics-engineer/templates/bus-assessment-template.md +134 -0
  43. package/roles/data-bus-avionics-engineer/tests/test_bundle_protocol.py +137 -0
  44. package/roles/data-bus-avionics-engineer/tests/test_data_bus_avionics_core.py +261 -0
  45. package/roles/data-bus-avionics-engineer/tests/test_role_data_bus_avionics_engineer.py +121 -0
  46. package/roles/do160-environmental-engineer/ROLE.md +136 -0
  47. package/roles/do160-environmental-engineer/SOURCES.md +30 -0
  48. package/roles/do160-environmental-engineer/cli.py +147 -0
  49. package/roles/do160-environmental-engineer/core/do160_environmental_core.py +1246 -0
  50. package/roles/do160-environmental-engineer/templates/environmental-qualification-plan-template.md +114 -0
  51. package/roles/do160-environmental-engineer/tests/test_do160_environmental_core.py +312 -0
  52. package/roles/do160-environmental-engineer/tests/test_role_do160_environmental_engineer.py +281 -0
  53. package/roles/do178c-cert-engineer/ROLE.md +120 -0
  54. package/roles/do178c-cert-engineer/SOURCES.md +16 -0
  55. package/roles/do178c-cert-engineer/cli.py +129 -0
  56. package/roles/do178c-cert-engineer/core/do178c_core.py +411 -0
  57. package/roles/do178c-cert-engineer/templates/psac-template.md +74 -0
  58. package/roles/do178c-cert-engineer/tests/test_do178c_cert_engineer_core.py +91 -0
  59. package/roles/do178c-cert-engineer/tests/test_role_do178c.py +109 -0
  60. package/roles/do254-hardware-engineer/ROLE.md +127 -0
  61. package/roles/do254-hardware-engineer/SOURCES.md +19 -0
  62. package/roles/do254-hardware-engineer/cli.py +308 -0
  63. package/roles/do254-hardware-engineer/core/do254_hardware_core.py +761 -0
  64. package/roles/do254-hardware-engineer/templates/phac-template.md +87 -0
  65. package/roles/do254-hardware-engineer/tests/test_do254_bundle_profile.py +152 -0
  66. package/roles/do254-hardware-engineer/tests/test_do254_hardware_core.py +200 -0
  67. package/roles/do254-hardware-engineer/tests/test_role_do254_hardware.py +108 -0
  68. package/roles/engineering-analysis-engineer/ROLE.md +154 -0
  69. package/roles/engineering-analysis-engineer/SOURCES.md +15 -0
  70. package/roles/engineering-analysis-engineer/cli.py +151 -0
  71. package/roles/engineering-analysis-engineer/core/engineering_analysis_engineer_core.py +866 -0
  72. package/roles/engineering-analysis-engineer/templates/analysis-memo-template.md +73 -0
  73. package/roles/engineering-analysis-engineer/tests/test_engineering_analysis_engineer_core.py +367 -0
  74. package/roles/engineering-analysis-engineer/tests/test_role_engineering_analysis_engineer.py +113 -0
  75. package/roles/flight-management-engineer/ROLE.md +160 -0
  76. package/roles/flight-management-engineer/SOURCES.md +30 -0
  77. package/roles/flight-management-engineer/cli.py +258 -0
  78. package/roles/flight-management-engineer/core/flight_management_core.py +1473 -0
  79. package/roles/flight-management-engineer/templates/route-assessment-template.md +94 -0
  80. package/roles/flight-management-engineer/tests/test_flight_management_core.py +437 -0
  81. package/roles/flight-management-engineer/tests/test_role_flight_management_engineer.py +123 -0
  82. package/roles/flight-mechanics-engineer/ROLE.md +162 -0
  83. package/roles/flight-mechanics-engineer/SOURCES.md +51 -0
  84. package/roles/flight-mechanics-engineer/cli.py +149 -0
  85. package/roles/flight-mechanics-engineer/core/flight_mechanics_core.py +1507 -0
  86. package/roles/flight-mechanics-engineer/templates/perf-sc-report-template.md +82 -0
  87. package/roles/flight-mechanics-engineer/tests/test_flight_mechanics_core.py +277 -0
  88. package/roles/flight-mechanics-engineer/tests/test_role_flight_mechanics_engineer.py +141 -0
  89. package/roles/flight-test-engineer/ROLE.md +137 -0
  90. package/roles/flight-test-engineer/SOURCES.md +24 -0
  91. package/roles/flight-test-engineer/cli.py +135 -0
  92. package/roles/flight-test-engineer/core/flight_test_core.py +931 -0
  93. package/roles/flight-test-engineer/templates/flight-test-template.md +143 -0
  94. package/roles/flight-test-engineer/tests/test_flight_test_engineer_core.py +227 -0
  95. package/roles/flight-test-engineer/tests/test_role_flight_test_engineer.py +150 -0
  96. package/roles/flight-test-performance-engineer/ROLE.md +165 -0
  97. package/roles/flight-test-performance-engineer/SOURCES.md +20 -0
  98. package/roles/flight-test-performance-engineer/cli.py +319 -0
  99. package/roles/flight-test-performance-engineer/core/flight_test_performance_core.py +1398 -0
  100. package/roles/flight-test-performance-engineer/templates/performance-report-template.md +105 -0
  101. package/roles/flight-test-performance-engineer/tests/test_flight_test_performance_engineer_core.py +308 -0
  102. package/roles/flight-test-performance-engineer/tests/test_role_flight_test_performance_engineer.py +165 -0
  103. package/roles/gnc-engineer/ROLE.md +141 -0
  104. package/roles/gnc-engineer/SOURCES.md +35 -0
  105. package/roles/gnc-engineer/cli.py +141 -0
  106. package/roles/gnc-engineer/core/gnc_core.py +1074 -0
  107. package/roles/gnc-engineer/templates/gnc-report-template.md +105 -0
  108. package/roles/gnc-engineer/tests/test_gnc_engineer_core.py +238 -0
  109. package/roles/gnc-engineer/tests/test_role_gnc_engineer.py +142 -0
  110. package/roles/guidance-engineer/ROLE.md +156 -0
  111. package/roles/guidance-engineer/SOURCES.md +43 -0
  112. package/roles/guidance-engineer/cli.py +280 -0
  113. package/roles/guidance-engineer/core/guidance_core.py +820 -0
  114. package/roles/guidance-engineer/templates/guidance-report-template.md +70 -0
  115. package/roles/guidance-engineer/tests/test_guidance_engineer_core.py +269 -0
  116. package/roles/guidance-engineer/tests/test_role_guidance_engineer.py +146 -0
  117. package/roles/high-speed-aerodynamics-engineer/ROLE.md +161 -0
  118. package/roles/high-speed-aerodynamics-engineer/SOURCES.md +18 -0
  119. package/roles/high-speed-aerodynamics-engineer/cli.py +282 -0
  120. package/roles/high-speed-aerodynamics-engineer/core/high_speed_aero_core.py +1566 -0
  121. package/roles/high-speed-aerodynamics-engineer/templates/high-speed-memo-template.md +75 -0
  122. package/roles/high-speed-aerodynamics-engineer/tests/test_bundle_protocol.py +103 -0
  123. package/roles/high-speed-aerodynamics-engineer/tests/test_high_speed_aero_core.py +280 -0
  124. package/roles/high-speed-aerodynamics-engineer/tests/test_role_high_speed_aerodynamics.py +125 -0
  125. package/roles/ndt-engineer/ROLE.md +157 -0
  126. package/roles/ndt-engineer/SOURCES.md +18 -0
  127. package/roles/ndt-engineer/cli.py +316 -0
  128. package/roles/ndt-engineer/core/ndt_core.py +1590 -0
  129. package/roles/ndt-engineer/templates/ndt-plan-template.md +109 -0
  130. package/roles/ndt-engineer/tests/test_ndt_cli_bundle.py +183 -0
  131. package/roles/ndt-engineer/tests/test_ndt_core.py +437 -0
  132. package/roles/ndt-engineer/tests/test_role_ndt_engineer.py +148 -0
  133. package/roles/numerical-analysis-engineer/ROLE.md +158 -0
  134. package/roles/numerical-analysis-engineer/SOURCES.md +19 -0
  135. package/roles/numerical-analysis-engineer/cli.py +414 -0
  136. package/roles/numerical-analysis-engineer/core/numerical_analysis_core.py +1220 -0
  137. package/roles/numerical-analysis-engineer/templates/verification-memo-template.md +120 -0
  138. package/roles/numerical-analysis-engineer/tests/test_bundle_protocol.py +157 -0
  139. package/roles/numerical-analysis-engineer/tests/test_numerical_analysis_core.py +337 -0
  140. package/roles/numerical-analysis-engineer/tests/test_role_numerical_analysis_engineer.py +117 -0
  141. package/roles/propulsion-engineer/ROLE.md +163 -0
  142. package/roles/propulsion-engineer/SOURCES.md +15 -0
  143. package/roles/propulsion-engineer/cli.py +168 -0
  144. package/roles/propulsion-engineer/core/propulsion_core.py +1291 -0
  145. package/roles/propulsion-engineer/templates/propulsion-report-template.md +92 -0
  146. package/roles/propulsion-engineer/tests/test_propulsion_engineer_core.py +294 -0
  147. package/roles/propulsion-engineer/tests/test_role_propulsion_engineer.py +106 -0
  148. package/roles/space-systems-engineer/ROLE.md +161 -0
  149. package/roles/space-systems-engineer/SOURCES.md +49 -0
  150. package/roles/space-systems-engineer/cli.py +173 -0
  151. package/roles/space-systems-engineer/core/space_systems_core.py +980 -0
  152. package/roles/space-systems-engineer/templates/space-systems-template.md +88 -0
  153. package/roles/space-systems-engineer/tests/test_role_space_systems_engineer.py +117 -0
  154. package/roles/space-systems-engineer/tests/test_space_systems_engineer_core.py +247 -0
  155. package/roles/state-estimation-engineer/ROLE.md +154 -0
  156. package/roles/state-estimation-engineer/SOURCES.md +27 -0
  157. package/roles/state-estimation-engineer/cli.py +326 -0
  158. package/roles/state-estimation-engineer/core/state_estimation_core.py +958 -0
  159. package/roles/state-estimation-engineer/templates/nav-state-estimator-design-template.md +76 -0
  160. package/roles/state-estimation-engineer/tests/test_bundle_protocol.py +110 -0
  161. package/roles/state-estimation-engineer/tests/test_role_state_estimation_engineer.py +127 -0
  162. package/roles/state-estimation-engineer/tests/test_state_estimation_engineer_core.py +231 -0
  163. package/roles/structures-loads-engineer/ROLE.md +171 -0
  164. package/roles/structures-loads-engineer/SOURCES.md +44 -0
  165. package/roles/structures-loads-engineer/cli.py +219 -0
  166. package/roles/structures-loads-engineer/core/structures_loads_core.py +1112 -0
  167. package/roles/structures-loads-engineer/templates/loads-strength-report-template.md +96 -0
  168. package/roles/structures-loads-engineer/tests/test_bundle_protocol.py +101 -0
  169. package/roles/structures-loads-engineer/tests/test_role_structures_loads_engineer.py +147 -0
  170. package/roles/structures-loads-engineer/tests/test_structures_loads_core.py +261 -0
@@ -0,0 +1,894 @@
1
+ #!/usr/bin/env python3
2
+ """airworthiness_core.py - Airworthiness Compliance Engineer executable core.
3
+
4
+ This is the role's ENGINE: given a certification item's project facts it
5
+ determines the applicable FAR-25 / CS-25 regulations, selects the means of
6
+ compliance per regulation, assigns the compliance document and an honest
7
+ proposed status, and BUILDS the airworthiness compliance matrix (the
8
+ certification matrix / compliance checklist deliverable). It also
9
+ gate-checks deliverables. Standalone: no external repo needed.
10
+
11
+ Domain rules encoded here are public process knowledge:
12
+ - Regulation numbers, section titles and subpart placement of 14 CFR
13
+ Part 25 (FAR-25) are public-domain (US government work, quotable with
14
+ citation; verified against eCFR). CS-25 mirrors FAR-25 with EU
15
+ amendments; referenced at rule level only.
16
+ - The means-of-compliance selection rules are a deterministic summary of
17
+ public certification practice (the six-class MOC scheme and the
18
+ analysis / test / inspection / similarity vocabulary). No verbatim AMC
19
+ or regulatory body text is reproduced.
20
+
21
+ Author: ashfordeOU
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import re
27
+ from dataclasses import dataclass, field
28
+ from datetime import date
29
+
30
+ def _today() -> str:
31
+ import os
32
+ from datetime import date
33
+ return os.environ.get("ROLE_GEN_DATE", date.today().isoformat())
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Domain tables (public-domain FAR-25 / CS-25 structure and practice)
37
+ # ---------------------------------------------------------------------------
38
+
39
+ # Severity of a failure condition -> development assurance level context
40
+ # (standard 25.1309 practice, as used in certification programs).
41
+ SEVERITY_TO_DAL = {
42
+ "catastrophic": "A",
43
+ "hazardous": "B",
44
+ "major": "C",
45
+ "minor": "D",
46
+ "no-safety-effect": "E",
47
+ "n/a": "n/a",
48
+ }
49
+ DALS = ("A", "B", "C", "D", "E", "n/a")
50
+
51
+ # Failure-condition severities that trigger the 25.1309 safety assessment
52
+ # (systems whose failure conditions are catastrophic, hazardous, or major).
53
+ SAFETY_SIGNIFICANT = ("catastrophic", "hazardous", "major")
54
+
55
+ # Means-of-compliance vocabulary, six-class scheme used in certification
56
+ # practice (summary of public EASA/FAA style MOC categories).
57
+ MOC_NAMES = {
58
+ 1: "engineering/analysis",
59
+ 2: "ground test",
60
+ 3: "flight test",
61
+ 4: "simulation/analysis tool",
62
+ 5: "certification by similarity",
63
+ 6: "safety assessment",
64
+ }
65
+ MOC_IDS = tuple(sorted(MOC_NAMES))
66
+
67
+ # Coarse method words accepted in a compliance matrix (FAA-style vocabulary
68
+ # per public airworthiness practice: analysis, test, inspection, similarity).
69
+ MOC_WORDS = (
70
+ "analysis",
71
+ "test",
72
+ "ground test",
73
+ "flight test",
74
+ "inspection",
75
+ "similarity",
76
+ "safety assessment",
77
+ "engineering/analysis",
78
+ "simulation/analysis tool",
79
+ "certification program demonstration",
80
+ )
81
+
82
+ # Item kinds a certification item can be (per-item MOC suitability rules).
83
+ ITEM_KINDS = (
84
+ "structure",
85
+ "systems",
86
+ "powerplant",
87
+ "equipment",
88
+ "software",
89
+ "hardware",
90
+ "performance",
91
+ "handling",
92
+ )
93
+
94
+ # FAR-25 subpart placement for the regulations this role routes on.
95
+ # Letters/names are the public FAR-25 structure (A General, C Structure,
96
+ # D Design and Construction, F Equipment, ...).
97
+ SUBPARTS = {
98
+ "A": "General",
99
+ "B": "Flight",
100
+ "C": "Structure",
101
+ "D": "Design and Construction",
102
+ "E": "Powerplant",
103
+ "F": "Equipment",
104
+ "G": "Operating Limitations and Information",
105
+ "H": "Electrical Wiring Interconnection Systems (EWIS)",
106
+ }
107
+
108
+ # Representative area mapping (which product area a paragraph belongs to),
109
+ # transport-airplane oriented per public FAR-25 structure.
110
+ AREA_PARAGRAPHS = {
111
+ "systems": ["25.1309", "25.1310", "25.1329"],
112
+ "flight-controls": ["25.671", "25.672", "25.675", "25.677", "25.679", "25.683"],
113
+ "structure": ["25.301", "25.303", "25.305", "25.307", "25.571", "25.629"],
114
+ "design-and-construction": ["25.601", "25.603", "25.605", "25.607", "25.609"],
115
+ "powerplant": ["25.901", "25.903", "25.933", "25.943", "25.1101"],
116
+ "equipment": ["25.1301", "25.1303", "25.1305", "25.1309", "25.1316"],
117
+ "operating-limitations": ["25.1501", "25.1503", "25.1511", "25.1521"],
118
+ "ewis": ["25.1701", "25.1703", "25.1707", "25.1709", "25.1711", "25.1713", "25.1717"],
119
+ }
120
+
121
+ # Regulation register: real FAR-25 paragraphs used by this role. Fields:
122
+ # subpart - public FAR-25 subpart letter
123
+ # area - product area key from AREA_PARAGRAPHS
124
+ # item_kind - item kind for MOC suitability
125
+ # severity - context severity of the paragraph in a typical systems
126
+ # installation (structure rows carry "n/a")
127
+ # novel_by_default - True when a retrofit of this kind is typically a new
128
+ # feature screened against the existing basis
129
+ # Section titles are the public eCFR titles (public domain, quotable).
130
+ REGS = {
131
+ "25.1301": {
132
+ "title": "Function and installation",
133
+ "subpart": "F",
134
+ "area": "equipment",
135
+ "item_kind": "equipment",
136
+ "severity": "n/a",
137
+ "compliance_document": "Equipment installation inspection records and "
138
+ "qualification test report (installation conformity)",
139
+ },
140
+ "25.1303": {
141
+ "title": "Flight and navigation instruments",
142
+ "subpart": "F",
143
+ "area": "equipment",
144
+ "item_kind": "equipment",
145
+ "severity": "n/a",
146
+ "compliance_document": "Instrument installation conformity records",
147
+ },
148
+ "25.1309": {
149
+ "title": "Equipment, systems, and installations",
150
+ "subpart": "F",
151
+ "area": "systems",
152
+ "item_kind": "systems",
153
+ "severity": "hazardous",
154
+ "compliance_document": "System safety assessment (SSA) per 25.1309 "
155
+ "with failure condition classification",
156
+ },
157
+ "25.1310": {
158
+ "title": "Power source capacity and distribution",
159
+ "subpart": "F",
160
+ "area": "systems",
161
+ "item_kind": "systems",
162
+ "severity": "major",
163
+ "compliance_document": "Electrical load analysis and power source "
164
+ "capacity report",
165
+ },
166
+ "25.1329": {
167
+ "title": "Flight guidance system (autopilot)",
168
+ "subpart": "F",
169
+ "area": "systems",
170
+ "item_kind": "systems",
171
+ "severity": "hazardous",
172
+ "compliance_document": "Autopilot ground/flight test report and SSA "
173
+ "input for the flight guidance system",
174
+ },
175
+ "25.671": {
176
+ "title": "General (flight control systems)",
177
+ "subpart": "D",
178
+ "area": "flight-controls",
179
+ "item_kind": "systems",
180
+ "severity": "hazardous",
181
+ "compliance_document": "Flight control integration analysis and test "
182
+ "report (control system failure assessment)",
183
+ },
184
+ "25.683": {
185
+ "title": "Operation tests",
186
+ "subpart": "D",
187
+ "area": "flight-controls",
188
+ "item_kind": "systems",
189
+ "severity": "n/a",
190
+ "compliance_document": "Control system operation test report",
191
+ },
192
+ "25.301": {
193
+ "title": "Loads",
194
+ "subpart": "C",
195
+ "area": "structure",
196
+ "item_kind": "structure",
197
+ "severity": "n/a",
198
+ "compliance_document": "Loads analysis report (change-effect "
199
+ "assessment on the affected structure)",
200
+ },
201
+ "25.571": {
202
+ "title": "Damage tolerance and fatigue evaluation of structure",
203
+ "subpart": "C",
204
+ "area": "structure",
205
+ "item_kind": "structure",
206
+ "severity": "n/a",
207
+ "compliance_document": "Damage tolerance / fatigue evaluation and "
208
+ "inspection program for affected structure",
209
+ },
210
+ "25.607": {
211
+ "title": "Fasteners",
212
+ "subpart": "D",
213
+ "area": "design-and-construction",
214
+ "item_kind": "structure",
215
+ "severity": "n/a",
216
+ "compliance_document": "Fastener standards compliance record",
217
+ },
218
+ "25.629": {
219
+ "title": "Aeroelastic stability requirements",
220
+ "subpart": "C",
221
+ "area": "structure",
222
+ "item_kind": "structure",
223
+ "severity": "n/a",
224
+ "compliance_document": "Flutter / aeroelastic stability analysis and "
225
+ "flight test clearance report",
226
+ },
227
+ }
228
+
229
+ # Per-regulation extra methods of compliance that real demonstration
230
+ # practice requires on top of the item-kind suitability set (original
231
+ # routing summary, e.g. flight test for autopilot operation tests).
232
+ REG_EXTRA_MOC = {
233
+ "25.1329": ["flight test"],
234
+ "25.683": ["ground test"],
235
+ "25.629": ["flight test"],
236
+ }
237
+
238
+ # Certification basis by aircraft type (public regulation mapping).
239
+ # Product airplane: transport -> Part 25 (FAR-25/CS-25); normal/utility/
240
+ # acrobatic/commuter -> Part 23; rotorcraft -> Part 27/29; engines Part 33;
241
+ # propellers Part 35. This role routes on transport-category airplanes
242
+ # (FAR-25 / CS-25) but keeps the full public mapping for screening.
243
+ AIRCRAFT_REGULATION = {
244
+ ("airplane", "transport"): "25",
245
+ ("airplane", "normal"): "23",
246
+ ("airplane", "utility"): "23",
247
+ ("airplane", "acrobatic"): "23",
248
+ ("airplane", "commuter"): "23",
249
+ ("rotorcraft", "normal"): "27",
250
+ ("rotorcraft", "transport"): "29",
251
+ ("engine", "n/a"): "33",
252
+ ("propeller", "n/a"): "35",
253
+ }
254
+ AIRCRAFT_ALIASES = {
255
+ "transport": ("airplane", "transport"),
256
+ "transport-category": ("airplane", "transport"),
257
+ "transport airplane": ("airplane", "transport"),
258
+ "large airplane": ("airplane", "transport"),
259
+ "normal": ("airplane", "normal"),
260
+ "normal-category": ("airplane", "normal"),
261
+ "rotorcraft": ("rotorcraft", "normal"),
262
+ "rotorcraft transport": ("rotorcraft", "transport"),
263
+ "engine": ("engine", "n/a"),
264
+ "propeller": ("propeller", "n/a"),
265
+ }
266
+
267
+ # Owners by product area (original role assignment table).
268
+ AREA_OWNER = {
269
+ "systems": "systems safety engineer",
270
+ "flight-controls": "flight controls engineer",
271
+ "structure": "structures engineer",
272
+ "design-and-construction": "design engineer",
273
+ "powerplant": "powerplant engineer",
274
+ "equipment": "equipment engineer",
275
+ "operating-limitations": "flight test engineer",
276
+ "ewis": "EWIS engineer",
277
+ }
278
+
279
+ # Statuses this role may write. A finding ("compliant") is NEVER written:
280
+ # that is the certification authority's call.
281
+ STATUS_PROPOSED = "proposed"
282
+ STATUS_VOCABULARY = ("proposed", "open", "in-work", "evidence-available")
283
+ FORBIDDEN_STATUS = ("compliant", "found", "approved", "issued")
284
+
285
+ # ---------------------------------------------------------------------------
286
+ # Core computations
287
+ # ---------------------------------------------------------------------------
288
+
289
+
290
+ def _canonical_aircraft(aircraft_type: str) -> tuple[str, str]:
291
+ """Map an aircraft_type string to (product_type, category)."""
292
+ key = (aircraft_type or "").strip().lower()
293
+ if key in AIRCRAFT_ALIASES:
294
+ return AIRCRAFT_ALIASES[key]
295
+ raise ValueError(
296
+ "unknown aircraft_type %r; expected one of %s"
297
+ % (aircraft_type, ", ".join(sorted(AIRCRAFT_ALIASES)))
298
+ )
299
+
300
+
301
+ def reg_number(reg) -> str:
302
+ """Normalize a regulation reference to a bare number (e.g. 25.1309)."""
303
+ if isinstance(reg, dict):
304
+ reg = reg.get("number") or reg.get("reg") or ""
305
+ text = str(reg or "").strip()
306
+ text = text.replace("FAR ", "").replace("CS-", "").replace("CS ", "")
307
+ match = re.fullmatch(r"(\d{2}\.\d{3,4})", text)
308
+ if match is None:
309
+ raise ValueError("invalid regulation reference %r" % (reg,))
310
+ return match.group(1)
311
+
312
+
313
+ def determine_applicability(reg, aircraft_type: str,
314
+ change_scope=None) -> dict:
315
+ """Determine whether a regulation applies to a change on an aircraft.
316
+
317
+ Returns a verdict dict {reg, aircraft_type, product, category,
318
+ applicable, part, reason}. Two gates:
319
+ 1. Aircraft-type gate: a FAR-25/CS-25 paragraph applies to
320
+ transport-category airplanes only; other categories screen to
321
+ their governing part (public regulation mapping).
322
+ 2. Change-scope gate (when ``change_scope`` is given): the reg's
323
+ product area must be inside the change scope (e.g. an autopilot
324
+ STC that touches systems/flight-controls/equipment screens out
325
+ pure-structure paragraphs such as 25.571).
326
+ """
327
+ number = reg_number(reg)
328
+ if number not in REGS:
329
+ raise ValueError(
330
+ "unknown regulation %r - not in the role's FAR-25 register"
331
+ % (number,)
332
+ )
333
+ product, category = _canonical_aircraft(aircraft_type)
334
+ governing = AIRCRAFT_REGULATION.get((product, category))
335
+ if governing is None:
336
+ raise ValueError(
337
+ "no governing airworthiness part for %s %s"
338
+ % (product, category)
339
+ )
340
+ if not number.startswith(governing + "."):
341
+ return {
342
+ "reg": number,
343
+ "aircraft_type": aircraft_type,
344
+ "product": product,
345
+ "category": category,
346
+ "applicable": False,
347
+ "part": governing,
348
+ "reason": (
349
+ "not applicable: %s is a Part 25 paragraph but a %s category "
350
+ "%s certifies under Part %s/CS-%s (the equivalent paragraph, "
351
+ "where one exists, lives in that part)"
352
+ % (number, category, product, governing, governing)
353
+ ),
354
+ }
355
+ # Part matches the aircraft type: now screen against the change scope.
356
+ if change_scope is not None:
357
+ area = REGS[number]["area"]
358
+ if area not in change_scope:
359
+ return {
360
+ "reg": number,
361
+ "aircraft_type": aircraft_type,
362
+ "product": product,
363
+ "category": category,
364
+ "applicable": False,
365
+ "part": governing,
366
+ "reason": (
367
+ "not applicable to this change: %s covers the %s area "
368
+ "but the change scope is limited to %s; screened out "
369
+ "unless the change also touches the %s area - confirm "
370
+ "with the certification authority"
371
+ % (number, area, ", ".join(sorted(change_scope)), area)
372
+ ),
373
+ }
374
+ return {
375
+ "reg": number,
376
+ "aircraft_type": aircraft_type,
377
+ "product": product,
378
+ "category": category,
379
+ "applicable": True,
380
+ "part": governing,
381
+ "reason": (
382
+ "applies: %s is a %s airworthiness paragraph and the product "
383
+ "is a %s category %s"
384
+ % (number, "FAR-25/CS-25" if governing == "25"
385
+ else "FAR-%s/CS-%s" % (governing, governing),
386
+ category, product)
387
+ ),
388
+ }
389
+
390
+
391
+ def _validate_kind_severity_dal(item_kind, severity, dal) -> None:
392
+ if item_kind not in ITEM_KINDS:
393
+ raise ValueError(
394
+ "unknown item_kind %r; expected one of %s"
395
+ % (item_kind, ", ".join(ITEM_KINDS))
396
+ )
397
+ if severity not in SEVERITY_TO_DAL:
398
+ raise ValueError(
399
+ "unknown severity %r; expected one of %s"
400
+ % (severity, ", ".join(sorted(SEVERITY_TO_DAL)))
401
+ )
402
+ if dal not in DALS:
403
+ raise ValueError(
404
+ "unknown development_assurance_level %r; expected one of %s"
405
+ % (dal, ", ".join(DALS))
406
+ )
407
+
408
+
409
+ def _dedupe(seq) -> list:
410
+ seen = []
411
+ for entry in seq:
412
+ if entry not in seen:
413
+ seen.append(entry)
414
+ return seen
415
+
416
+
417
+ def moc_suitability(item_kind: str, severity: str = "n/a",
418
+ dal: str = "n/a", novel: bool = False) -> list:
419
+ """Recommended MOC ids (ordered, primary first) for an item kind.
420
+
421
+ Deterministic routing summary of public certification practice:
422
+ structure - analysis primary with static/ground test evidence (novel
423
+ items add flight test); systems - analysis plus the MOC 6 safety
424
+ assessment for catastrophic/hazardous failures (simulation acceptable
425
+ for DAL C-E, a test MOC is added for novel items); powerplant - bench/
426
+ ground and flight test with supporting analysis; equipment -
427
+ environmental qualification test (DO-160 style) with analysis support;
428
+ software - DO-178C lifecycle data; hardware - DO-254 style analysis
429
+ plus verification test.
430
+ """
431
+ _validate_kind_severity_dal(item_kind, severity, dal)
432
+ if item_kind == "structure":
433
+ return [1, 2, 3] if novel else [1, 2]
434
+ if item_kind == "systems":
435
+ rec = [1]
436
+ if severity in ("catastrophic", "hazardous"):
437
+ rec.append(6)
438
+ elif dal in ("C", "D", "E"):
439
+ rec.append(4)
440
+ if novel:
441
+ rec.append(2)
442
+ return _dedupe(rec)
443
+ if item_kind == "powerplant":
444
+ return [2, 3, 1]
445
+ if item_kind == "equipment":
446
+ return [2, 1]
447
+ if item_kind == "software":
448
+ return [1, 4]
449
+ if item_kind == "hardware":
450
+ return [1, 2]
451
+ return [3, 1, 4] # performance, handling
452
+
453
+
454
+ def safety_assessment_required(severity: str) -> bool:
455
+ """25.1309 practice: catastrophic/hazardous/major failure conditions
456
+ require the safety assessment."""
457
+ if severity not in SEVERITY_TO_DAL:
458
+ raise ValueError("unknown failure-condition severity %r" % (severity,))
459
+ return severity in SAFETY_SIGNIFICANT
460
+
461
+
462
+ def severity_to_dal(severity: str) -> str:
463
+ if severity not in SEVERITY_TO_DAL:
464
+ raise ValueError("unknown severity %r" % (severity,))
465
+ return SEVERITY_TO_DAL[severity]
466
+
467
+
468
+ def select_moc(reg, cert_basis: str = "FAR-25",
469
+ severity: str | None = None,
470
+ dal: str | None = None,
471
+ novel: bool = False) -> dict:
472
+ """Select the means of compliance for a regulation.
473
+
474
+ Returns a dict {reg, item_kind, severity, dal, moc_ids, methods,
475
+ moc_display, safety_assessment}. ``methods`` are coarse method words
476
+ (analysis/test/inspection vocabulary); ``moc_display`` renders the
477
+ six-class MOC scheme when the basis is CS-25 (per AMC practice) and
478
+ the coarse vocabulary for FAR-25.
479
+ """
480
+ number = reg_number(reg)
481
+ if number not in REGS:
482
+ raise ValueError(
483
+ "unknown regulation %r - not in the role's FAR-25 register"
484
+ % (number,)
485
+ )
486
+ record = REGS[number]
487
+ item_kind = record["item_kind"]
488
+ resolved_severity = severity or record["severity"]
489
+ resolved_dal = dal or severity_to_dal(resolved_severity)
490
+ ids = moc_suitability(item_kind, resolved_severity, resolved_dal, novel)
491
+ ids = _dedupe(ids + [moc_id(m) for m in REG_EXTRA_MOC.get(number, [])
492
+ if moc_id(m) not in ids])
493
+ methods = [moc_name(i) for i in ids]
494
+ basis = str(cert_basis or "FAR-25").upper()
495
+ if "CS" in basis:
496
+ display = " + ".join(
497
+ "MOC %d %s" % (i, MOC_NAMES[i]) for i in ids
498
+ ) + " (per AMC-25 style acceptable means of compliance)"
499
+ else:
500
+ display = " + ".join(methods)
501
+ return {
502
+ "reg": number,
503
+ "item_kind": item_kind,
504
+ "severity": resolved_severity,
505
+ "dal": resolved_dal,
506
+ "moc_ids": ids,
507
+ "methods": methods,
508
+ "moc_display": display,
509
+ "safety_assessment": safety_assessment_required(resolved_severity),
510
+ }
511
+
512
+
513
+ def moc_id(word: str) -> int:
514
+ """Map a coarse method word to its MOC class id."""
515
+ mapping = {
516
+ "analysis": 1,
517
+ "engineering/analysis": 1,
518
+ "ground test": 2,
519
+ "test": 2,
520
+ "flight test": 3,
521
+ "simulation": 4,
522
+ "simulation/analysis tool": 4,
523
+ "similarity": 5,
524
+ "safety assessment": 6,
525
+ }
526
+ key = word.strip().lower()
527
+ if key not in mapping:
528
+ raise ValueError("unknown means of compliance %r" % (word,))
529
+ return mapping[key]
530
+
531
+
532
+ def moc_name(moc_id_val: int) -> str:
533
+ if moc_id_val not in MOC_NAMES:
534
+ raise ValueError("unknown MOC id %r" % (moc_id_val,))
535
+ return MOC_NAMES[moc_id_val]
536
+
537
+
538
+ def moc_is_valid(method: str) -> bool:
539
+ return method.strip().lower() in MOC_WORDS
540
+
541
+
542
+ def methods_are_valid(methods) -> bool:
543
+ return all(moc_is_valid(m) for m in methods)
544
+
545
+
546
+ def basis_name(aircraft_type: str, jurisdiction: str) -> str:
547
+ """Certification basis regulation id for a transport airplane."""
548
+ product, category = _canonical_aircraft(aircraft_type)
549
+ governing = AIRCRAFT_REGULATION.get((product, category))
550
+ if governing != "25":
551
+ raise ValueError(
552
+ "role routes FAR-25/CS-25 transport work; %r certifies under "
553
+ "Part %s" % (aircraft_type, governing)
554
+ )
555
+ if jurisdiction not in ("FAA", "EASA"):
556
+ raise ValueError("jurisdiction must be FAA or EASA, got %r" % (jurisdiction,))
557
+ return "FAR-25" if jurisdiction == "FAA" else "CS-25"
558
+
559
+
560
+ # ---------------------------------------------------------------------------
561
+ # Compliance matrix builder: produces the actual deliverable content
562
+ # ---------------------------------------------------------------------------
563
+
564
+
565
+ @dataclass
566
+ class AirworthinessItem:
567
+ """Project facts the role needs to build the compliance matrix."""
568
+ item_name: str
569
+ description: str = ""
570
+ aircraft_type: str = "transport"
571
+ jurisdiction: str = "FAA"
572
+ certification_path: str = "STC" # type certificate / STC / amended TC / TSO
573
+ regulation_ids: list = field(default_factory=list)
574
+ change_scope: list = field(default_factory=list) # areas the change touches
575
+ severity_overrides: dict = field(default_factory=dict)
576
+ novel_regs: list = field(default_factory=list)
577
+ special_conditions: list = field(default_factory=list) # list[str]
578
+ elos_items: list = field(default_factory=list) # list[str]
579
+
580
+ def basis(self) -> str:
581
+ return basis_name(self.aircraft_type, self.jurisdiction)
582
+
583
+
584
+ def compliance_row(reg, item: AirworthinessItem) -> dict:
585
+ """Build one matrix row: reg -> applicability -> MoC -> document ->
586
+ proposed status."""
587
+ number = reg_number(reg)
588
+ if number not in REGS:
589
+ raise ValueError(
590
+ "unknown regulation %r - not in the role's FAR-25 register"
591
+ % (number,)
592
+ )
593
+ record = REGS[number]
594
+ verdict = determine_applicability(
595
+ number, item.aircraft_type, change_scope=item.change_scope or None
596
+ )
597
+ severity = item.severity_overrides.get(number, record["severity"])
598
+ novel = number in (item.novel_regs or [])
599
+ moc = select_moc(number, item.basis(), severity=severity, novel=novel)
600
+ dal = moc["dal"]
601
+ if not verdict["applicable"]:
602
+ return {
603
+ "reg": number,
604
+ "title": record["title"],
605
+ "subpart": record["subpart"],
606
+ "area": record["area"],
607
+ "applicable": False,
608
+ "reason": verdict["reason"],
609
+ "item_kind": record["item_kind"],
610
+ "severity": severity,
611
+ "dal": dal,
612
+ "moc_ids": [],
613
+ "methods": [],
614
+ "moc_display": "not applicable - screened out",
615
+ "safety_assessment": False,
616
+ "compliance_document": "not required (not applicable)",
617
+ "status": STATUS_PROPOSED,
618
+ "owner": "-",
619
+ }
620
+ return {
621
+ "reg": number,
622
+ "title": record["title"],
623
+ "subpart": record["subpart"],
624
+ "area": record["area"],
625
+ "applicable": True,
626
+ "reason": verdict["reason"],
627
+ "item_kind": record["item_kind"],
628
+ "severity": severity,
629
+ "dal": dal,
630
+ "moc_ids": moc["moc_ids"],
631
+ "methods": moc["methods"],
632
+ "moc_display": moc["moc_display"],
633
+ "safety_assessment": moc["safety_assessment"],
634
+ "compliance_document": record["compliance_document"],
635
+ "status": STATUS_PROPOSED,
636
+ "owner": AREA_OWNER.get(record["area"], "compliance engineer"),
637
+ }
638
+
639
+
640
+ def _row_matches_item(row: dict, item: AirworthinessItem) -> bool:
641
+ """Gate helper: is the row's applicability determination consistent
642
+ with the item scope? (all rows built from the same item are)."""
643
+ if row["reg"] not in REGS:
644
+ return False
645
+ return True
646
+
647
+
648
+ def build_matrix(item: AirworthinessItem) -> dict:
649
+ """Build the full compliance matrix content model."""
650
+ rows = [compliance_row(r, item) for r in item.regulation_ids]
651
+ applicable = [r for r in rows if r["applicable"]]
652
+ covered = [r for r in applicable if r["methods"] and r["compliance_document"]]
653
+ coverage = (len(covered) / len(applicable)) if applicable else 0.0
654
+ issues = [
655
+ "%s: no accepted means of compliance yet" % r["reg"]
656
+ for r in applicable
657
+ if not r["methods"]
658
+ ]
659
+ special = item.special_conditions or []
660
+ elos = item.elos_items or []
661
+ return {
662
+ "document_type": "Airworthiness compliance checklist / certification matrix",
663
+ "status": "draft-for-review",
664
+ "item": item.item_name,
665
+ "item_description": item.description,
666
+ "aircraft_type": item.aircraft_type,
667
+ "jurisdiction": item.jurisdiction,
668
+ "certification_basis": item.basis(),
669
+ "certification_path": item.certification_path,
670
+ "special_conditions": special,
671
+ "elos_items": elos,
672
+ "rows": rows,
673
+ "applicable_count": len(applicable),
674
+ "row_count": len(rows),
675
+ "coverage": coverage,
676
+ "issues": issues,
677
+ "generated": _today(),
678
+ }
679
+
680
+
681
+ def render_matrix_markdown(model: dict) -> str:
682
+ """Render the compliance matrix content model as markdown."""
683
+ basis = model["certification_basis"]
684
+ regs_applicable = ", ".join(
685
+ r["reg"] for r in model["rows"] if r["applicable"]
686
+ ) or "none"
687
+ sc = ", ".join(model["special_conditions"]) or "none identified"
688
+ elos = ", ".join(model["elos_items"]) or "none proposed"
689
+ lines = [
690
+ "# Airworthiness Compliance Matrix",
691
+ "",
692
+ f"**Item:** {model['item']}",
693
+ f"**Aircraft type:** {model['aircraft_type']} (jurisdiction "
694
+ f"{model['jurisdiction']})",
695
+ f"**Certification basis:** {basis}",
696
+ f"**Certification path:** {model['certification_path']}",
697
+ f"**Applicable regulations (proposed):** {regs_applicable}",
698
+ f"**Special conditions:** {sc}",
699
+ f"**Equivalent level of safety (ELOS) findings:** {elos}",
700
+ f"**Status:** {model['status']}",
701
+ f"**Coverage:** {model['coverage']:.0%} of applicable regulations "
702
+ "have a proposed means of compliance and compliance document",
703
+ "",
704
+ "## Certification basis",
705
+ "",
706
+ f"- Aircraft/system: {model['item']}",
707
+ f"- Certification basis reference (TC/STC/amendment level): "
708
+ f"{basis}, {model['certification_path']}",
709
+ f"- Applicable regulations (list with amendment): "
710
+ f"{regs_applicable} (amendment level per the basis)",
711
+ f"- Special conditions: {sc}",
712
+ f"- Equivalent level of safety findings: {elos}",
713
+ "",
714
+ "## Compliance matrix",
715
+ "",
716
+ "| Reg | Applicable? | Means of compliance | Compliance document | "
717
+ "Status (proposed/open) | Owner |",
718
+ "|---|---|---|---|---|---|",
719
+ ]
720
+ for row in model["rows"]:
721
+ reg_cell = "%s (Subpart %s - %s)" % (
722
+ row["reg"], row["subpart"], SUBPARTS[row["subpart"]]
723
+ )
724
+ if row["applicable"]:
725
+ app_cell = "YES"
726
+ moc_cell = row["moc_display"]
727
+ if row["safety_assessment"]:
728
+ moc_cell += " (safety assessment required)"
729
+ doc_cell = row["compliance_document"]
730
+ dal_cell = "proposed (severity %s / DAL %s)" % (
731
+ row["severity"], row["dal"]
732
+ )
733
+ owner_cell = row["owner"]
734
+ else:
735
+ app_cell = "NO"
736
+ moc_cell = "n/a"
737
+ doc_cell = "not required (screened out: %s)" % row["reason"]
738
+ dal_cell = "proposed (n/a)"
739
+ owner_cell = row["owner"]
740
+ lines.append(
741
+ "| %s | %s | %s | %s | %s | %s |"
742
+ % (reg_cell, app_cell, moc_cell, doc_cell, dal_cell, owner_cell)
743
+ )
744
+ lines += [
745
+ "",
746
+ "## Notes",
747
+ "",
748
+ "- Status vocabulary: proposed | open | in-work | evidence-available.",
749
+ "- Every row above is a PROPOSED determination by the compliance "
750
+ "engineer (this role). The certification authority makes the binding "
751
+ "applicability and compliance findings.",
752
+ "- NEVER mark a row \"compliant/found\" without evidence - that is "
753
+ "the authority's finding, not ours.",
754
+ "- Severity and DAL context are proposed inputs to the 25.1309 "
755
+ "safety assessment, subject to the system safety process.",
756
+ "",
757
+ "## Open items (gap report)",
758
+ "",
759
+ ]
760
+ if model["issues"]:
761
+ lines += ["- " + issue for issue in model["issues"]]
762
+ else:
763
+ lines.append("- No open gaps in this draft; confirm every row with "
764
+ "the certification authority before submission.")
765
+ lines += [
766
+ "",
767
+ "---",
768
+ f"*Generated by Aero Agent Roles airworthiness-compliance-engineer "
769
+ f"core ({model['generated']}). DRAFT - for human compliance engineer "
770
+ "review. Not a compliance finding or an approval.*",
771
+ ]
772
+ return "\n".join(lines)
773
+
774
+
775
+ # ---------------------------------------------------------------------------
776
+ # Evidence gate checkers: verify a deliverable meets the role's gates
777
+ # ---------------------------------------------------------------------------
778
+
779
+ GATE_CHECKS = {
780
+ "basis_identified": "certification basis (FAR-25/CS-25) is identified",
781
+ "rows_present": "at least one matrix row exists",
782
+ "regs_real": "every row references a real FAR-25 paragraph",
783
+ "all_applicable_have_moc": "every applicable row has a means of compliance",
784
+ "all_applicable_have_doc": "every applicable row has a compliance document",
785
+ "moc_vocabulary_valid": "means of compliance use the real vocabulary",
786
+ "statuses_honest": "no row claims a compliant/found finding",
787
+ "sign_off_honest": "document is marked draft-for-review, not approval",
788
+ }
789
+
790
+
791
+ def check_matrix(model: dict) -> dict:
792
+ """Run the evidence gates against a compliance matrix model."""
793
+ rows = model.get("rows", [])
794
+ applicable = [r for r in rows if r.get("applicable")]
795
+ results = {
796
+ "basis_identified": bool(model.get("certification_basis")),
797
+ "rows_present": bool(rows),
798
+ "regs_real": all(
799
+ r.get("reg") in REGS and r.get("reg") in _REAL_POOL
800
+ for r in rows
801
+ ),
802
+ "all_applicable_have_moc": all(
803
+ bool(r.get("methods")) for r in applicable
804
+ ),
805
+ "all_applicable_have_doc": all(
806
+ bool(r.get("compliance_document")) for r in applicable
807
+ ),
808
+ "moc_vocabulary_valid": all(
809
+ methods_are_valid(r["methods"]) for r in applicable
810
+ ),
811
+ "statuses_honest": all(
812
+ (r.get("status") or "").lower() not in FORBIDDEN_STATUS
813
+ for r in rows
814
+ ),
815
+ "sign_off_honest": model.get("status") == "draft-for-review",
816
+ }
817
+ results["all_pass"] = all(results.values())
818
+ return results
819
+
820
+
821
+ # All real paragraphs any reg row may reference (routing check pool).
822
+ _REAL_POOL = [p for area in AREA_PARAGRAPHS.values() for p in area]
823
+
824
+
825
+ def check_matrix_markdown(md_text: str) -> dict:
826
+ """Gate-check the rendered markdown deliverable."""
827
+ low = md_text.lower()
828
+ has_reg = bool(re.search(r"\b25\.\d{3,4}\b", md_text))
829
+ checks = {
830
+ "has_title": "airworthiness compliance matrix" in low,
831
+ "has_basis": "certification basis" in low,
832
+ "has_reg_numbers": has_reg,
833
+ "has_table_header": ("| reg |" in low and "means of compliance" in low),
834
+ "has_status_word": "proposed" in low,
835
+ "no_blank_fields": "___" not in md_text,
836
+ "has_draft_marker": "draft" in low,
837
+ "has_not_approval": "not a compliance finding or an approval" in low,
838
+ }
839
+ checks["all_pass"] = all(checks.values())
840
+ return checks
841
+
842
+
843
+ def validate_deliverable_text(md_text: str) -> dict:
844
+ """Public entry point used by gate tooling: check a matrix document."""
845
+ return check_matrix_markdown(md_text)
846
+
847
+
848
+ # ---------------------------------------------------------------------------
849
+ # Example project (for tests and worked-example generation)
850
+ # ---------------------------------------------------------------------------
851
+
852
+
853
+ def example_item() -> AirworthinessItem:
854
+ return AirworthinessItem(
855
+ item_name="Autopilot flight guidance system retrofit (STC)",
856
+ description="Supplemental type certificate for a digital autopilot / "
857
+ "flight guidance system on a transport-category airplane: "
858
+ "autopilot computer, mode selector, servo actuators on "
859
+ "the flight controls, and annunciators. The change is a "
860
+ "major change by someone other than the type certificate "
861
+ "holder, so it takes the supplemental type certificate "
862
+ "(STC) path against the existing FAR-25 basis. The STC "
863
+ "delta touches the systems, flight-controls and equipment "
864
+ "areas; no primary structure is modified.",
865
+ aircraft_type="transport",
866
+ jurisdiction="FAA",
867
+ certification_path="STC",
868
+ change_scope=["systems", "flight-controls", "equipment"],
869
+ regulation_ids=[
870
+ # applicable to the STC delta:
871
+ "25.1301", "25.1309", "25.1329", "25.671", "25.683",
872
+ # screened-out candidates that exercise the applicability logic:
873
+ "25.301", "25.571", "25.607",
874
+ ],
875
+ severity_overrides={}, # use the register context severities
876
+ novel_regs=["25.1329"], # new flight guidance feature on the type
877
+ special_conditions=[],
878
+ elos_items=[],
879
+ )
880
+
881
+
882
+ def example_matrix_markdown() -> str:
883
+ return render_matrix_markdown(build_matrix(example_item()))
884
+
885
+
886
+ if __name__ == "__main__":
887
+ item = example_item()
888
+ model = build_matrix(item)
889
+ md = render_matrix_markdown(model)
890
+ print("CERTIFICATION BASIS: %s" % model["certification_basis"])
891
+ print("APPLICABLE REGS: %d/%d" % (model["applicable_count"], model["row_count"]))
892
+ print("COVERAGE: %.2f" % model["coverage"])
893
+ print("GATES: %s" % check_matrix(model))
894
+ print("RENDERED: %d chars" % len(md))