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,326 @@
1
+ #!/usr/bin/env python3
2
+ """cli.py - run the State Estimation Engineer role.
3
+
4
+ Usage:
5
+ python3 cli.py build [--out <file.md>] [--dt <s>] [--steps <n>]
6
+ [--q <m2/s3>] [--r <m2>] [--alpha <a>]
7
+ [--bundle] [--no-dispatch] [--profile <p.json>]
8
+ # build the Navigation State Estimator Design Report
9
+ # --bundle also emit evidence/{model,gates,provenance}.json
10
+ # (docs/PROTOCOL.md v1)
11
+ # --profile apply a program/customer profile header
12
+ python3 cli.py check --file <file.md> # gate-check an existing report
13
+
14
+ The role ENGINE (core/state_estimation_core.py) does the work standalone:
15
+ complementary-filter attitude design, constant-velocity Kalman recursion,
16
+ alpha-beta (Benedict-Bordner/Kalata) gains, EKF linearization, RTS
17
+ smoothing, observability, and the evidence gates. When AeroSkills is
18
+ present (AEROSKILLS_DEV or ~/AeroSkills), the CLI dispatches the bound
19
+ estimation-filtering leaf logic files and cross-checks identical
20
+ computations (alpha-beta gains, Kalman recursion, EKF Jacobian, NEES,
21
+ effective sample size); agreement is recorded in provenance.json.
22
+ """
23
+ import argparse
24
+ import importlib.util
25
+ import json
26
+ import os
27
+ import sys
28
+
29
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
30
+ "core"))
31
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
32
+ "..", "..", "scripts"))
33
+ import state_estimation_core as core # noqa: E402
34
+ import evidence # noqa: E402
35
+
36
+ ROLE_SLUG = "state-estimation-engineer"
37
+ AEROSKILLS = os.environ.get("AEROSKILLS_DEV", os.path.expanduser("~/AeroSkills"))
38
+ SKILLS_RELEASE = "v1.3.0+"
39
+
40
+
41
+ def _load_logic(leaf: str):
42
+ """Import the first *_logic.py under an AeroSkills leaf (or None)."""
43
+ logic_dir = os.path.join(AEROSKILLS, "skills", leaf, "scripts")
44
+ if not os.path.isdir(logic_dir):
45
+ return None
46
+ for lf in sorted(f for f in os.listdir(logic_dir)
47
+ if f.endswith("_logic.py")):
48
+ spec = importlib.util.spec_from_file_location(
49
+ "role_dispatch", os.path.join(logic_dir, lf))
50
+ mod = importlib.util.module_from_spec(spec)
51
+ try:
52
+ spec.loader.exec_module(mod)
53
+ return mod, lf
54
+ except Exception:
55
+ continue
56
+ return None
57
+
58
+
59
+ def _dispatch_row(leaf, item, model, spec) -> dict | None:
60
+ """Run one bound-leaf computation and compare with the core value.
61
+
62
+ spec: {function, skill_kwargs(item)->kwargs, select(result)->float,
63
+ core(item, model)->float, note} -- select turns the leaf
64
+ return value into the comparable scalar.
65
+ """
66
+ loaded = _load_logic(leaf)
67
+ if loaded is None:
68
+ return None
69
+ mod, logic_file = loaded
70
+ if not hasattr(mod, spec["function"]):
71
+ return None
72
+ try:
73
+ skill_val = spec["select"](
74
+ getattr(mod, spec["function"])(**spec["skill_kwargs"](item)))
75
+ core_val = spec["core"](item, model)
76
+ delta = abs(float(core_val) - float(skill_val))
77
+ return {
78
+ "leaf": leaf,
79
+ "skill_md": "SKILL.md",
80
+ "logic_file": "scripts/" + logic_file,
81
+ "function": spec["function"],
82
+ "dispatched": True,
83
+ "cross_check_point": spec["note"](item),
84
+ "core_value": round(float(core_val), 9),
85
+ "skill_value": round(float(skill_val), 9),
86
+ "delta": round(delta, 12),
87
+ "agrees": delta <= spec.get("tolerance", 1e-6),
88
+ "tolerance": spec.get("tolerance", 1e-6),
89
+ "skills_release": SKILLS_RELEASE,
90
+ }
91
+ except Exception as e: # noqa: BLE001 - dispatch must never crash build
92
+ return {"leaf": leaf, "function": spec["function"],
93
+ "dispatched": True, "error": str(e)[:150],
94
+ "agrees": False}
95
+
96
+
97
+ def dispatch_crosschecks(item, model) -> list:
98
+ """Cross-check the core against all bound leaves with shared math."""
99
+
100
+ def ab_leaf(lam):
101
+ return {"leaf": "gnc-autonomy/estimation-filtering/alpha-beta-filter",
102
+ "function": "gains_from_tracking_index",
103
+ "skill_kwargs": lambda it: {"tracking_index": it.tracking_index()},
104
+ "select": lambda v: v["alpha"],
105
+ "core": lambda it, m: core.kalata_gains(it.tracking_index())[
106
+ "alpha"],
107
+ "note": lambda it: "Kalata alpha from lambda=%.6f"
108
+ % it.tracking_index(),
109
+ "tolerance": 1e-6}
110
+
111
+ def beta_bb(lam):
112
+ return {"leaf": "gnc-autonomy/estimation-filtering/alpha-beta-filter",
113
+ "function": "steady_state_gains",
114
+ "skill_kwargs": lambda it: {"alpha": it.alpha},
115
+ "select": lambda v: v["beta"],
116
+ "core": lambda it, m: core.steady_state_beta(it.alpha),
117
+ "note": lambda it: "Benedict-Bordner beta at alpha=%.3f"
118
+ % it.alpha,
119
+ "tolerance": 1e-6}
120
+
121
+ def kf_final(lam):
122
+ return {"leaf": "gnc-autonomy/estimation-filtering/rts-smoother",
123
+ "function": "forward_kalman",
124
+ "skill_kwargs": lambda it: {
125
+ "measurements": it.measurement_sequence(), "dt": it.dt,
126
+ "q": it.process_noise_intensity, "r": it.meas_variance,
127
+ "x0": [0.0, 0.0],
128
+ "p0": [[it.initial_pos_var, 0.0],
129
+ [0.0, it.initial_vel_var]]},
130
+ "select": lambda v: v[-1]["P_filt"][0][0],
131
+ "core": lambda it, m: m["kalman"]["final"]["pos_var_after"],
132
+ "note": lambda it: "final position variance, CV Kalman "
133
+ "recursion (step %d)" % it.horizon_steps,
134
+ "tolerance": 1e-6}
135
+
136
+ def ekf_h(lam):
137
+ op = [float(v) for v in item.ekf_op_point]
138
+ return {"leaf":
139
+ "gnc-autonomy/estimation-filtering/extended-kalman-filter",
140
+ "function": "jacobian_h",
141
+ "skill_kwargs": lambda it: {
142
+ "h": _skill_bearing_range, "x": op},
143
+ "select": lambda v: v[0][0],
144
+ "core": lambda it, m: m["ekf"]["H"][0][0],
145
+ "note": lambda it: "EKF Jacobian H[0][0] at the operating "
146
+ "point (range row)",
147
+ "tolerance": 1e-6}
148
+
149
+ def nees_chk(lam):
150
+ op = [float(v) for v in item.ekf_op_point]
151
+ p2 = [[item.initial_pos_var, 0.0], [0.0, item.initial_pos_var]]
152
+ return {"leaf":
153
+ "gnc-autonomy/estimation-filtering/unscented-kalman-filter",
154
+ "function": "nees",
155
+ "skill_kwargs": lambda it: {"x_est": op[:2], "p_est": p2,
156
+ "x_true": [op[0] - 5.0,
157
+ op[1] + 2.0]},
158
+ "select": lambda v: float(v),
159
+ "core": lambda it, m: core.nees(op[:2], p2,
160
+ [op[0] - 5.0, op[1] + 2.0]),
161
+ "note": lambda it: "NEES consistency sample on the "
162
+ "position subspace",
163
+ "tolerance": 1e-6}
164
+
165
+ def ess_chk(lam):
166
+ w = [1.0 / item.particle_count] * item.particle_count
167
+ return {"leaf": "gnc-autonomy/estimation-filtering/particle-filter",
168
+ "function": "effective_sample_size",
169
+ "skill_kwargs": lambda it: {"weights": w},
170
+ "select": lambda v: float(v),
171
+ "core": lambda it, m: core.effective_sample_size(w),
172
+ "note": lambda it: "ESS of a uniform %d-particle ensemble"
173
+ % it.particle_count,
174
+ "tolerance": 1e-9}
175
+
176
+ specs = [beta_bb(item), ab_leaf(item), kf_final(item), ekf_h(item),
177
+ nees_chk(item), ess_chk(item)]
178
+ rows = []
179
+ for s in specs:
180
+ row = _dispatch_row(s["leaf"], item, model, s)
181
+ if row:
182
+ rows.append(row)
183
+ return rows
184
+
185
+
186
+ def _skill_bearing_range(x):
187
+ """Bearing/range measurement used only for the dispatch comparison
188
+ (same model as the extended-kalman-filter leaf)."""
189
+ import math
190
+ return [math.hypot(x[0], x[1]), math.atan2(x[1], x[0])]
191
+
192
+
193
+ def _provenance(item, model, dispatch_rows):
194
+ return {
195
+ "role": ROLE_SLUG,
196
+ "core": {
197
+ "file": "core/state_estimation_core.py",
198
+ "functions": ["run_cv_kalman", "kalata_gains",
199
+ "steady_state_beta", "ekf_correct", "nees",
200
+ "effective_sample_size", "steady_state_verdict",
201
+ "rts_smooth", "observability_rank"],
202
+ "version": "0.1.0",
203
+ },
204
+ "skills": dispatch_rows,
205
+ "cross_checked": bool(dispatch_rows) and all(
206
+ r.get("dispatched") and not r.get("error") and r.get("agrees")
207
+ for r in dispatch_rows),
208
+ "disclaimer": "DRAFT for human review. Not an approval document.",
209
+ }
210
+
211
+
212
+ def cmd_build(args):
213
+ item = core.example_item()
214
+ if args.dt:
215
+ item.dt = float(args.dt)
216
+ if args.steps:
217
+ item.horizon_steps = int(args.steps)
218
+ if args.q:
219
+ item.process_noise_intensity = float(args.q)
220
+ if args.r:
221
+ item.meas_variance = float(args.r)
222
+ if args.alpha:
223
+ item.alpha = float(args.alpha)
224
+ model = core.build_report(item)
225
+ md = core.render_report_markdown(model)
226
+
227
+ profile = None
228
+ try:
229
+ profile = evidence.load_profile(args.profile)
230
+ except ValueError as e:
231
+ print(f"error: bad profile: {e}")
232
+ return 1
233
+ if profile:
234
+ model["profile"] = {
235
+ "customer": profile.get("customer"),
236
+ "program": profile.get("program"),
237
+ "basis": profile.get("basis"),
238
+ "authority": profile.get("authority"),
239
+ "der": profile.get("der"),
240
+ "document_prefix": profile.get("document_prefix"),
241
+ "revision": profile.get("revision"),
242
+ }
243
+ md = evidence.profile_header_block(profile) + md
244
+
245
+ gates = core.check_report(model)
246
+
247
+ dispatch_rows = []
248
+ if not args.no_dispatch:
249
+ dispatch_rows = dispatch_crosschecks(item, model)
250
+
251
+ if args.out:
252
+ with open(args.out, "w") as f:
253
+ f.write(md if md.endswith("\n") else md + "\n")
254
+ print(f"built Navigation State Estimator Design Report -> {args.out}")
255
+ print(f"estimator class: {model['estimator_class']}")
256
+ print(f"kalman final gain: {model['kalman']['final']['gain']} "
257
+ f"pos var: {model['kalman']['final']['pos_var_after']:.4f} m^2")
258
+ print(f"gates: all_pass={gates['all_pass']} {gates}")
259
+ if args.bundle:
260
+ prov = _provenance(item, model, dispatch_rows)
261
+ paths = evidence.write_bundle(
262
+ args.out, ROLE_SLUG,
263
+ "Navigation State Estimator Design Report", model, gates,
264
+ prov)
265
+ print(f"bundle: model={paths['model']}")
266
+ print(f" gates={paths['gates']}")
267
+ print(f" provenance={paths['provenance']}")
268
+ for row in dispatch_rows:
269
+ if "error" in row:
270
+ print(f"cross-check: {row['leaf']} error: {row['error']}")
271
+ else:
272
+ print(f"cross-check: {row['leaf']} "
273
+ f"{row['function']} core={row['core_value']} "
274
+ f"skill={row['skill_value']} "
275
+ f"delta={row['delta']} agrees={row['agrees']}")
276
+ if not dispatch_rows:
277
+ print("cross-check: not dispatched (no AeroSkills logic)")
278
+ else:
279
+ print(md)
280
+ return 0 if gates["all_pass"] else 1
281
+
282
+
283
+ def cmd_check(args):
284
+ if not os.path.exists(args.file):
285
+ print(f"file not found: {args.file}")
286
+ return 1
287
+ md = open(args.file).read()
288
+ gates = core.check_report_markdown(md)
289
+ for name, ok in gates.items():
290
+ if name != "all_pass":
291
+ print(f"{'PASS' if ok else 'FAIL'} {name}")
292
+ print(f"RESULT: {'PASS' if gates['all_pass'] else 'FAIL'}")
293
+ return 0 if gates["all_pass"] else 1
294
+
295
+
296
+ def main():
297
+ p = argparse.ArgumentParser(
298
+ description="State Estimation Engineer role")
299
+ sub = p.add_subparsers(dest="cmd", required=True)
300
+
301
+ b = sub.add_parser("build")
302
+ b.add_argument("--out", default="", help="output file (default: stdout)")
303
+ b.add_argument("--dt", default="", help="nav update interval in s")
304
+ b.add_argument("--steps", default="", help="design horizon in steps")
305
+ b.add_argument("--q", default="", help="process noise intensity m^2/s^3")
306
+ b.add_argument("--r", default="", help="measurement variance m^2")
307
+ b.add_argument("--alpha", default="", help="alpha-beta smoothing factor")
308
+ b.add_argument("--bundle", action="store_true",
309
+ help="emit evidence/{model,gates,provenance}.json")
310
+ b.add_argument("--no-dispatch", action="store_true",
311
+ help="skip dispatching bound AeroSkills logic")
312
+ b.add_argument("--profile", default="",
313
+ help="program profile JSON (docs/PROFILE-SCHEMA.md)")
314
+ b.set_defaults(fn=cmd_build)
315
+
316
+ c = sub.add_parser("check")
317
+ c.add_argument("--file", required=True,
318
+ help="report markdown to gate-check")
319
+ c.set_defaults(fn=cmd_check)
320
+
321
+ args = p.parse_args()
322
+ sys.exit(args.fn(args))
323
+
324
+
325
+ if __name__ == "__main__":
326
+ main()