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.")