import streamlit as st
import pandas as pd
import math
from fpdf import FPDF
import os
import matplotlib.pyplot as plt
from groq import Groq
# ========== 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 ==========
groq_api_key = os.getenv("GROQ_API_KEY")
groq_client = Groq(api_key=groq_api_key) if groq_api_key else 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, R, S, E, corrosion):
return (P * R) / (S * E - 0.6 * P) + corrosion
def head_thickness(P, R, S, E, corrosion, head_type):
if head_type == "Ellipsoidal":
return (0.5 * P * R) / (S * E - 0.1 * P) + corrosion
elif head_type == "Torispherical":
return (0.885 * P * R) / (S * E - 0.1 * P) + corrosion
elif head_type == "Hemispherical":
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")
R = st.number_input("Internal Radius (mm)", value=1000.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")
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, R, S, E, corrosion)
st.metric("Required Shell Thickness (mm)", f"{t_shell_calc:.2f}")
# --- HEAD TAB ---
with tabs[1]:
t_head_calc = head_thickness(P, R, S, E, corrosion, head_type)
st.metric(f"Required {head_type} Head Thickness (mm)", 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:", "â
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) else "â No")
# --- SUMMARY TAB ---
with tabs[5]:
summary_data = {
"Shell Thickness": t_shell_calc,
"Head Thickness": t_head_calc,
"Nozzle Safe": safe_nozzle,
"PWHT Required": pwht_required(thickness),
"Impact Test Required": impact_test_required(thickness),
}
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")
pdf.chapter_body(str(summary_data))
pdf_file = "results.pdf"
pdf.output(pdf_file)
with open(pdf_file, "rb") as f:
st.download_button("đ Download PDF Report", f, "results.pdf")
# --- AI EXPLANATION TAB ---
with tabs[6]:
st.markdown("### đ¤ Ask AI for Explanation")
if groq_client:
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}"
chat_completion = groq_client.chat.completions.create(
messages=[{"role": "user", "content": prompt}],
model="llama-3.1-8b-instant",
)
explanation = chat_completion.choices[0].message.content
st.success("â
AI Explanation Generated Below")
st.write(explanation)
if st.session_state.ai_done:
st.info("⨠AI explanation already generated. Rerun to refresh.")
else:
st.info("âšī¸ Add your GROQ_API_KEY in Hugging Face secrets to enable AI explanations.")
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}.")