Spaces:
Sleeping
Sleeping
File size: 8,620 Bytes
741324e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
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("<h1 style='text-align: center;'>🛠️ ASME CALCULATOR</h1>", unsafe_allow_html=True)
st.markdown(
"<p style='text-align: center;'>This tool calculates <b>required thicknesses, nozzle reinforcement, "
"PWHT, and impact test requirements</b><br>using ASME Section VIII Division 1 formulas.</p>",
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.")
|