cigawa commited on
Commit
f4e616a
·
verified ·
1 Parent(s): 8d67b02

Update packages.txt

Browse files
Files changed (1) hide show
  1. packages.txt +6 -330
packages.txt CHANGED
@@ -1,330 +1,6 @@
1
- """
2
- Apogee Labs — Vibration Test Automation MVP
3
-
4
- End-to-end demo:
5
- Inputs (form + file uploads + engineer prompt)
6
- -> [optional] Claude parses uploaded docs
7
- -> Claude designs a structured fixture spec
8
- -> CadQuery builds real STEP/STL/SVG geometry
9
- -> Profile engine computes the random vibration PSD + Grms + notches
10
- -> Claude writes design recommendations and the draft test report
11
- -> Engineer edits + rates output (RLHF feedback capture)
12
-
13
- Run:
14
- pip install -r requirements.txt
15
- export ANTHROPIC_API_KEY=sk-ant-... # optional; mock without it
16
- streamlit run app/main.py
17
- """
18
-
19
- import os
20
- import sys
21
- import json
22
- import tempfile
23
-
24
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
25
-
26
- import streamlit as st
27
- import pandas as pd
28
-
29
- from unified_retriever import UnifiedRetriever
30
- from claude_client import generate_fixture_spec, generate_prose
31
- from doc_parser import extract_with_claude
32
- from feedback import save_feedback, feedback_stats
33
- from cad_engine import (export_fixture, CADQUERY_AVAILABLE,
34
- CADQUERY_IMPORT_ERROR)
35
- from profile_engine import compute_profile, profile_as_table
36
- from templates import (
37
- FIXTURE_SPEC_SYSTEM, build_fixture_spec_prompt,
38
- RECOMMENDATIONS_SYSTEM, build_recommendations_prompt,
39
- REPORT_SYSTEM, build_report_prompt,
40
- )
41
-
42
- st.set_page_config(page_title="Apogee Labs — Vibration Test Automation",
43
- page_icon="🛰️", layout="wide")
44
-
45
- # On Streamlit Cloud, API keys arrive via st.secrets; copy them into the
46
- # environment variables the rest of the code (and the SDKs) read.
47
- # Locally, an already-set environment variable takes precedence.
48
- try:
49
- for _k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY", "VOYAGE_API_KEY"):
50
- if not os.environ.get(_k) and _k in st.secrets:
51
- os.environ[_k] = st.secrets[_k]
52
- except Exception:
53
- pass # st.secrets may not exist locally; that's fine
54
-
55
- # --- session state ----------------------------------------------------------
56
- for key in ("results", "extracted"):
57
- st.session_state.setdefault(key, None)
58
-
59
- # --- header -----------------------------------------------------------------
60
- st.title("🛰️ Apogee Labs — Vibration Test Automation")
61
- st.caption(
62
- "MVP: fixture CAD design · random vibration profile · automated test report. "
63
- "Grounded in public NASA/DoD standards. "
64
- "**All output is a DRAFT requiring qualified test engineer review.**"
65
- )
66
-
67
- key_present = bool(os.environ.get("ANTHROPIC_API_KEY"))
68
- c1, c2 = st.columns(2)
69
- with c1:
70
- if key_present:
71
- st.success("Anthropic API key detected — live generation enabled.")
72
- else:
73
- st.warning("No ANTHROPIC_API_KEY — fixture/profile run live; "
74
- "AI design text & doc parsing are mocked.")
75
- with c2:
76
- if CADQUERY_AVAILABLE:
77
- st.success("CadQuery available — real CAD (STEP/STL) generation enabled.")
78
- else:
79
- st.error(f"CadQuery unavailable — CAD disabled. ({CADQUERY_IMPORT_ERROR}) "
80
- "Run on Streamlit Cloud (Linux) where it installs cleanly.")
81
-
82
- # ============================================================================
83
- # 1. INPUTS
84
- # ============================================================================
85
- st.header("1. Inputs")
86
-
87
- colA, colB = st.columns(2)
88
- with colA:
89
- article_name = st.text_input("Test article name", "3U CubeSat")
90
- mass_kg = st.number_input("Test article mass (kg)", min_value=0.0,
91
- value=4.0, step=0.5)
92
- test_level = st.selectbox("Test level",
93
- ["qualification", "protoflight", "acceptance"])
94
- with colB:
95
- orbit = st.selectbox("Orbit / destination",
96
- ["LEO", "SSO", "MEO", "GEO", "Lunar", "Not specified"])
97
- launch_vehicle = st.selectbox(
98
- "Launch vehicle",
99
- ["Falcon 9", "Electron", "Vulcan", "New Glenn", "Not specified / TBD"])
100
- resonances_str = st.text_input(
101
- "Known resonant frequencies (Hz, comma-sep) — optional",
102
- placeholder="e.g. 320, 610")
103
-
104
- engineer_prompt = st.text_area(
105
- "Test engineer request",
106
- value="Design me a custom aluminum fixture to mount this article to the shaker.",
107
- height=70)
108
-
109
- uploads = st.file_uploader(
110
- "Upload mission specs / launch vehicle guide / test article datasheet "
111
- "(PDF or image) — optional",
112
- type=["pdf", "png", "jpg", "jpeg"], accept_multiple_files=True)
113
-
114
- # optional: parse uploads
115
- if uploads and st.button("📄 Parse uploaded documents"):
116
- if not key_present:
117
- st.warning("Document parsing needs an API key. Skipping.")
118
- else:
119
- merged = {}
120
- for uf in uploads:
121
- with tempfile.NamedTemporaryFile(
122
- delete=False, suffix=os.path.splitext(uf.name)[1]) as tmp:
123
- tmp.write(uf.getbuffer())
124
- tmp_path = tmp.name
125
- with st.spinner(f"Parsing {uf.name}..."):
126
- res = extract_with_claude(tmp_path)
127
- if res["ok"]:
128
- merged.update({k: v for k, v in res["data"].items() if v})
129
- else:
130
- st.error(f"{uf.name}: {res['error']}")
131
- os.unlink(tmp_path)
132
- st.session_state.extracted = merged or None
133
- if merged:
134
- st.success("Extracted parameters:")
135
- st.json(merged)
136
-
137
- # ============================================================================
138
- # 2. GENERATE
139
- # ============================================================================
140
- st.header("2. Generate")
141
-
142
- if st.button("⚙️ Generate Fixture + Profile + Report", type="primary"):
143
- resonances = []
144
- for tok in resonances_str.split(","):
145
- tok = tok.strip()
146
- if tok:
147
- try:
148
- resonances.append(float(tok))
149
- except ValueError:
150
- pass
151
-
152
- inputs = {
153
- "test_article_name": article_name,
154
- "mass_kg": mass_kg,
155
- "test_level": test_level,
156
- "orbit": orbit,
157
- "launch_vehicle": launch_vehicle,
158
- "known_resonances_hz": resonances or "Not specified",
159
- }
160
-
161
- retriever = UnifiedRetriever()
162
- chunks = retriever.all_chunks()
163
-
164
- with st.spinner("Designing fixture..."):
165
- spec_prompt = build_fixture_spec_prompt(
166
- inputs, engineer_prompt, st.session_state.extracted)
167
- spec_res = generate_fixture_spec(FIXTURE_SPEC_SYSTEM, spec_prompt, inputs)
168
- spec = spec_res["spec"]
169
-
170
- cad_paths = None
171
- cad_error = None
172
- if CADQUERY_AVAILABLE:
173
- with st.spinner("Building CAD geometry (STEP / STL / preview)..."):
174
- try:
175
- out_dir = os.path.join(tempfile.gettempdir(), "apogee_cad")
176
- cad_paths = export_fixture(spec, out_dir,
177
- f"fixture_{article_name.replace(' ','_')}")
178
- except Exception as e: # noqa: BLE001
179
- cad_error = str(e)
180
-
181
- with st.spinner("Computing vibration profile..."):
182
- profile = compute_profile(test_level, mass_kg=mass_kg,
183
- resonances_hz=resonances)
184
-
185
- with st.spinner("Writing design recommendations..."):
186
- rec_prompt = build_recommendations_prompt(inputs, spec.to_dict(), chunks)
187
- rec = generate_prose(RECOMMENDATIONS_SYSTEM, rec_prompt,
188
- label="recommendations")
189
-
190
- with st.spinner("Generating draft test report..."):
191
- profile_dict = {
192
- "level": profile.level,
193
- "duration_s_per_axis": profile.duration_s_per_axis,
194
- "overall_grms": profile.overall_grms,
195
- "mass_attenuation_db": profile.mass_attenuation_db,
196
- "breakpoints": profile_as_table(profile),
197
- "notches": profile.notches,
198
- "notes": profile.notes,
199
- }
200
- rep_prompt = build_report_prompt(inputs, spec.to_dict(), profile_dict,
201
- rec["text"], chunks)
202
- report = generate_prose(REPORT_SYSTEM, rep_prompt, label="report",
203
- max_tokens=3000)
204
-
205
- st.session_state.results = {
206
- "inputs": inputs, "spec": spec, "spec_mode": spec_res["mode"],
207
- "cad_paths": cad_paths, "cad_error": cad_error,
208
- "profile": profile, "profile_dict": profile_dict,
209
- "recommendations": rec["text"], "report": report["text"],
210
- }
211
-
212
- # ============================================================================
213
- # 3. RESULTS
214
- # ============================================================================
215
- res = st.session_state.results
216
- if res:
217
- st.header("3. Results")
218
- tab1, tab2, tab3 = st.tabs(
219
- ["🔧 Fixture Design", "📈 Vibration Profile", "📄 Test Report"])
220
-
221
- # ---- Fixture --------------------------------------------------------
222
- with tab1:
223
- spec = res["spec"]
224
- st.subheader("Custom Fixture Design")
225
- if res["spec_mode"] != "live":
226
- st.info(f"Fixture spec mode: {res['spec_mode']} "
227
- "(set API key for AI-tailored design).")
228
- lc, rc = st.columns([1, 1])
229
- with lc:
230
- if res["cad_paths"] and os.path.exists(res["cad_paths"]["svg"]):
231
- with open(res["cad_paths"]["svg"], "r", encoding="utf-8") as f:
232
- st.image(f.read(), caption="Fixture preview (isometric)")
233
- elif res["cad_error"]:
234
- st.error(f"CAD build error: {res['cad_error']}")
235
- else:
236
- st.info("CAD preview unavailable in this environment.")
237
- with rc:
238
- st.markdown(f"**Material:** {spec.material}")
239
- st.markdown(f"**Est. mass:** {spec.estimated_mass_kg()} kg")
240
- st.markdown(f"**Base:** {spec.base_length_mm}×{spec.base_width_mm}"
241
- f"×{spec.base_thickness_mm} mm")
242
- st.markdown(f"**Boss:** {spec.boss_length_mm}×{spec.boss_width_mm}"
243
- f"×{spec.boss_height_mm} mm")
244
- st.markdown(f"**Table bolts:** {spec.table_bolt_pattern.spacing_x_mm}"
245
- f"×{spec.table_bolt_pattern.spacing_y_mm} mm, "
246
- f"⌀{spec.table_bolt_pattern.hole_dia_mm} mm")
247
- st.markdown(f"**Article bolts:** {spec.article_bolt_pattern.spacing_x_mm}"
248
- f"×{spec.article_bolt_pattern.spacing_y_mm} mm, "
249
- f"⌀{spec.article_bolt_pattern.hole_dia_mm} mm")
250
- if spec.rationale:
251
- st.markdown(f"**Design rationale:** {spec.rationale}")
252
-
253
- if res["cad_paths"]:
254
- d1, d2 = st.columns(2)
255
- with d1:
256
- with open(res["cad_paths"]["step"], "rb") as f:
257
- st.download_button("⬇️ Download STEP", f.read(),
258
- file_name="fixture.step")
259
- with d2:
260
- with open(res["cad_paths"]["stl"], "rb") as f:
261
- st.download_button("⬇️ Download STL", f.read(),
262
- file_name="fixture.stl")
263
-
264
- st.divider()
265
- st.markdown("### Design Recommendations")
266
- st.markdown(res["recommendations"])
267
-
268
- # ---- Profile --------------------------------------------------------
269
- with tab2:
270
- profile = res["profile"]
271
- st.subheader("Random Vibration Test Profile")
272
- m1, m2, m3 = st.columns(3)
273
- m1.metric("Overall Grms", profile.overall_grms)
274
- m2.metric("Duration/axis", f"{profile.duration_s_per_axis} s")
275
- m3.metric("Mass atten.", f"{profile.mass_attenuation_db} dB")
276
-
277
- df = pd.DataFrame(profile_as_table(profile))
278
- # log-log PSD plot
279
- chart_df = df.rename(columns={"Frequency (Hz)": "freq",
280
- "ASD (g^2/Hz)": "asd"}).set_index("freq")
281
- st.line_chart(chart_df)
282
- st.caption("PSD breakpoints (GEVS generalized workmanship envelope, "
283
- "adjusted for level and mass).")
284
- st.dataframe(df, use_container_width=True)
285
-
286
- if profile.notches:
287
- st.markdown("**Suggested notches:**")
288
- st.dataframe(pd.DataFrame(profile.notches), use_container_width=True)
289
- for n in profile.notes:
290
- st.markdown(f"- {n}")
291
-
292
- # ---- Report ---------------------------------------------------------
293
- with tab3:
294
- st.subheader("Draft Test Report")
295
- st.markdown(res["report"])
296
- st.download_button("⬇️ Download report (Markdown)", res["report"],
297
- file_name="vibration_test_report.md",
298
- mime="text/markdown")
299
-
300
- # ---- Feedback / RLHF loop ------------------------------------------
301
- st.divider()
302
- st.header("4. Test Engineer Feedback (improves the model)")
303
- with st.form("feedback_form"):
304
- usefulness = st.slider("How useful was this output?", 1, 5, 4)
305
- was_edited = st.checkbox("I would edit/correct this before use")
306
- what_improve = st.text_area("What should be improved?", height=70)
307
- why_changes = st.text_area(
308
- "If you'd change the design/profile, what and why?", height=70)
309
- submitted = st.form_submit_button("💾 Submit feedback")
310
- if submitted:
311
- rec = save_feedback({
312
- "inputs": res["inputs"],
313
- "fixture_spec": res["spec"].to_dict(),
314
- "profile": res["profile_dict"],
315
- "usefulness": usefulness,
316
- "was_edited": was_edited,
317
- "what_improve": what_improve,
318
- "why_changes": why_changes,
319
- })
320
- if rec["ok"]:
321
- st.success(f"Feedback saved (id {rec['id'][:8]}). "
322
- "This becomes a training example.")
323
- else:
324
- st.error(f"Save failed: {rec['error']}")
325
-
326
- stats = feedback_stats()
327
- if stats["count"]:
328
- st.caption(f"Feedback collected: {stats['count']} · "
329
- f"avg usefulness {stats['avg_usefulness']} · "
330
- f"edited fraction {stats['edited_fraction']}")
 
1
+ libgl1
2
+ libglu1-mesa
3
+ libxrender1
4
+ libxext6
5
+ libsm6
6
+ libice6