|
|
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 |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="ASME Calculator", layout="wide") |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
groq_api_key = os.getenv("GROQ_API_KEY") |
|
|
groq_client = Groq(api_key=groq_api_key) if groq_api_key else None |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 = st.tabs(["Shell", "Head", "Nozzle", "PWHT", "Impact Test", "Summary", "AI Explanation"]) |
|
|
|
|
|
if st.session_state.run_done: |
|
|
|
|
|
with tabs[0]: |
|
|
t_shell_calc = shell_thickness(P, R, S, E, corrosion) |
|
|
st.metric("Required Shell Thickness (mm)", f"{t_shell_calc:.2f}") |
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
with tabs[3]: |
|
|
st.write("PWHT Required:", "β
Yes" if pwht_required(thickness) else "β No") |
|
|
|
|
|
|
|
|
with tabs[4]: |
|
|
st.write("Impact Test Required:", "β
Yes" if impact_test_required(thickness) else "β No") |
|
|
|
|
|
|
|
|
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 = df_summary.to_csv(index=False).encode("utf-8") |
|
|
st.download_button("π₯ Download Results (CSV)", csv, "results.csv") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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}.") |
|
|
|