import streamlit as st import pandas as pd from fpdf import FPDF import io import openai # ========== PAGE CONFIG ========== st.set_page_config(page_title="ASME Calculator", layout="wide") # App Title + Description st.markdown("

🛠️ ASME CALCULATOR

", unsafe_allow_html=True) st.markdown( "

This tool calculates required thicknesses, nozzle reinforcement, " "PWHT, and impact test requirements
using ASME Section VIII Division 1 formulas.

", unsafe_allow_html=True ) # ========== API CLIENT ========== if "OPENAI_API_KEY" in st.secrets: openai.api_key = st.secrets["OPENAI_API_KEY"] else: openai.api_key = None # ========== PDF GENERATOR ========== class PDF(FPDF): def __init__(self): super().__init__() self.add_page() self.set_font("Helvetica", "", 12) def header(self): self.set_font("Helvetica", "B", 14) self.cell(0, 10, "ASME VIII Div.1 Vessel Design Report", 0, 1, "C") def chapter_title(self, title): self.set_font("Helvetica", "B", 12) self.cell(0, 10, title, 0, 1, "L") def chapter_body(self, body): self.set_font("Helvetica", "", 11) self.multi_cell(0, 8, body) # ========== CALCULATION FUNCTIONS ========== def shell_thickness(P, D, S, E, corrosion): R = D / 2 # convert diameter to radius return (P * R) / (S * E - 0.6 * P) + corrosion def head_thickness(P, D, S, E, corrosion, head_type): R = D / 2 if head_type == "Ellipsoidal": # UG-32 return (0.5 * P * R) / (S * E - 0.1 * P) + corrosion elif head_type == "Torispherical": # Approx formula return (0.885 * P * R) / (S * E - 0.1 * P) + corrosion elif head_type == "Hemispherical": # UG-32 return (P * R) / (2 * S * E - 0.2 * P) + corrosion return None def nozzle_reinforcement(P, d, t_shell, t_nozzle, S, E): return (P * d) / (2 * S * E) <= (t_shell + t_nozzle) def pwht_required(thickness, material="CS"): return material == "CS" and thickness > 38 def impact_test_required(thickness, MDMT=-20, material="CS"): return material == "CS" and (MDMT < -29 and thickness > 12) # ========== SESSION STATE ========== if "run_done" not in st.session_state: st.session_state.run_done = False if "ai_done" not in st.session_state: st.session_state.ai_done = False # ========== SIDEBAR INPUTS ========== with st.sidebar.expander("📥 Manual Design Inputs", expanded=True): input_mode = st.radio("Input Mode:", ["Manual Entry", "Upload CSV"]) run_calculation = False if input_mode == "Manual Entry": P = st.number_input("Design Pressure (MPa)", value=2.0, format="%.2f") D = st.number_input("Internal Diameter (mm)", value=2000.0, format="%.1f") S = st.number_input("Allowable Stress (MPa)", value=120.0, format="%.1f") corrosion = st.number_input("Corrosion Allowance (mm)", value=1.5, format="%.2f") joint_method = st.radio("Joint Efficiency Selection", ["Preset (UW-12)", "Manual Entry"]) if joint_method == "Preset (UW-12)": E = st.selectbox("Select E (Joint Efficiency)", [1.0, 0.85, 0.7, 0.65, 0.6, 0.45]) else: E = st.number_input("Manual Joint Efficiency (0-1)", value=0.85, min_value=0.1, max_value=1.0) head_type = st.selectbox("Head Type", ["Ellipsoidal", "Torispherical", "Hemispherical"]) d_nozzle = st.number_input("Nozzle Diameter (mm)", value=200.0, format="%.1f") t_shell = st.number_input("Shell Thickness Provided (mm)", value=12.0, format="%.1f") t_nozzle = st.number_input("Nozzle Thickness Provided (mm)", value=10.0, format="%.1f") thickness = st.number_input("Governing Thickness (mm)", value=40.0, format="%.1f") MDMT = st.number_input("MDMT (°C)", value=-20.0, format="%.1f") if st.button("🚀 Run Calculation", use_container_width=True): st.session_state.run_done = True run_calculation = True if st.session_state.run_done: st.success("✅ Calculations completed! See results in the tabs.") else: uploaded_file = st.file_uploader("Upload CSV File", type=["csv"]) if uploaded_file: df = pd.read_csv(uploaded_file) st.dataframe(df.head()) if st.button("🚀 Run Calculation", use_container_width=True): st.session_state.run_done = True run_calculation = True if st.session_state.run_done: st.success("✅ Calculations completed! See results in the tabs.") # ========== TABS ========== tabs = st.tabs(["Shell", "Head", "Nozzle", "PWHT", "Impact Test", "Summary", "AI Explanation"]) if st.session_state.run_done: # --- SHELL TAB --- with tabs[0]: t_shell_calc = shell_thickness(P, D, S, E, corrosion) st.metric("Required Shell Thickness (mm) [UG-27]", f"{t_shell_calc:.2f}") # --- HEAD TAB --- with tabs[1]: t_head_calc = head_thickness(P, D, S, E, corrosion, head_type) st.metric(f"Required {head_type} Head Thickness (mm) [UG-32]", f"{t_head_calc:.2f}") # --- NOZZLE TAB --- with tabs[2]: safe_nozzle = nozzle_reinforcement(P, d_nozzle, t_shell, t_nozzle, S, E) st.write("Nozzle Reinforcement Check [UG-37]:", "✅ Safe" if safe_nozzle else "❌ Not Safe") # --- PWHT TAB --- with tabs[3]: st.write("PWHT Required:", "✅ Yes" if pwht_required(thickness) else "❌ No") # --- IMPACT TEST TAB --- with tabs[4]: st.write("Impact Test Required:", "✅ Yes" if impact_test_required(thickness, MDMT) else "❌ No") # --- SUMMARY TAB --- with tabs[5]: summary_data = { "Shell Thickness (UG-27)": round(t_shell_calc, 2), "Head Thickness (UG-32)": round(t_head_calc, 2), "Nozzle Safe (UG-37)": safe_nozzle, "PWHT Required": pwht_required(thickness), "Impact Test Required": impact_test_required(thickness, MDMT), } df_summary = pd.DataFrame([summary_data]) st.dataframe(df_summary) # CSV export csv = df_summary.to_csv(index=False).encode("utf-8") st.download_button("📥 Download Results (CSV)", csv, "results.csv") # PDF export pdf = PDF() pdf.chapter_title("Calculation Summary") for k, v in summary_data.items(): pdf.chapter_body(f"{k}: {v}") pdf_bytes = pdf.output(dest="S").encode("latin1") st.download_button("📄 Download PDF Report", pdf_bytes, "results.pdf", "application/pdf") # --- AI EXPLANATION TAB --- with tabs[6]: st.markdown("### 🤖 Ask AI for Explanation") if not openai.api_key: st.warning("⚠️ Add your OpenAI API key in Streamlit secrets to enable AI explanations.") else: if st.button("✨ Ask AI", use_container_width=True): st.session_state.ai_done = True with st.spinner("AI is preparing explanation..."): prompt = f"Explain these ASME vessel design results in simple terms: {summary_data}" try: chat_completion = openai.ChatCompletion.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": "You are an ASME design expert who explains results clearly."}, {"role": "user", "content": prompt} ], temperature=0.3 ) explanation = chat_completion["choices"][0]["message"]["content"] st.success("✅ AI Explanation Generated Below") st.write(explanation) except Exception as e: st.error(f"⚠️ Error generating AI explanation: {e}") if st.session_state.ai_done: st.info("✨ AI explanation already generated. Rerun to refresh.") else: # Placeholders for i, msg in enumerate([ "Shell results", "Head results", "Nozzle results", "PWHT decision", "Impact Test decision", "Summary", "AI explanation" ]): with tabs[i]: st.info(f"Run calculation to see {msg}.") # ========== FOOTER ========== st.markdown("---") st.caption("Disclaimer: This tool is for demo/educational purposes. Final design must be verified by a qualified engineer per ASME VIII Div.1.")