import streamlit as st import pandas as pd import numpy as np import math from io import BytesIO st.set_page_config(page_title="UG-37 Nozzle Reinforcement Calculator", layout="wide") st.title("UG-37 Nozzle Reinforcement Calculator — Streamlit (Hugging Face)") st.caption("This app is a developer-ready skeleton: calculation hooks are provided.\nIMPORTANT: replace the placeholder formulas with the official UG-37 formulas from the ASME Code before using for design.") # --------------------------- # Helpers # --------------------------- def to_si_length(val, unit): # convert inches to mm if needed if unit == 'in': return val * 25.4 return val def to_si_pressure(val, unit): # convert psi to MPa if unit == 'psi': return val * 0.00689476 return val # Placeholder: conservative demo required-area formula for internal pressure # *** DO NOT USE FOR DESIGN. Replace with UG-37(c) exact formula. *** def required_area_internal_demo(P, d, S, F=1.0): # very conservative demo: required area proportional to pressure * diameter / allowable stress # Units: P in MPa, d in mm, S in MPa -> result in mm^2 # Formula (demo): A_req = F * (P * d) / S * 1000 return F * (P * d) / S * 1000.0 # Placeholder: demo external pressure formula def required_area_external_demo(P_ext, d, S, F=1.0): # external pressure often more critical; demo uses same shape return F * (abs(P_ext) * d) / S * 1200.0 # Simple available-area calculators (geometric approximations) def calc_A1(Do, tn, d): # A1 — area of shell material around the opening (approx): # approximate annulus area associated with thickness tn over an effective ring width = tn # This is a placeholder conservative estimate: area = circumference * tn * tn ring_width = tn circumference = math.pi * Do return circumference * ring_width def calc_A2_pad(d, pad_OD, pad_t): # A2 — pad area contribution (approx) = pad ring area (projected radial area) if pad_OD is None: return 0.0 return math.pi * ( (pad_OD/2)**2 - (d/2)**2 ) * (pad_t / (pad_t + 1e-9)) def calc_A_welds(perimeter, leg, include_welds): if not include_welds: return 0.0 # approximate weld throat area = perimeter * leg return perimeter * leg # --------------------------- # Sidebar inputs # --------------------------- with st.sidebar.form('settings'): st.header('Global settings') units = st.selectbox('Units system', ['SI (mm, MPa)', 'Imperial (in, psi)']) unit_len = 'mm' if units.startswith('SI') else 'in' unit_pres = 'MPa' if units.startswith('SI') else 'psi' include_welds = st.checkbox('Include weld areas in available reinforcement', value=False) f_mode = st.selectbox('F-factor mode', ['Figure UG-37 lookup (manual entry)', 'Force F = 1.0']) if f_mode.startswith('Figure'): F_val = st.number_input('F factor (enter value from Figure UG-37)', min_value=0.01, value=1.0, format='%.3f') else: F_val = 1.0 demo_mode = st.checkbox('Run demo (placeholder formulas) — DO NOT USE FOR DESIGN', value=True) st.form_submit_button('Apply') # --------------------------- # Main inputs # --------------------------- st.header('Inputs') col1, col2 = st.columns(2) with col1: st.subheader('Vessel / Shell') Do = st.number_input(f'Vessel diameter (Do) [{unit_len}]', value=1000.0) tn = st.number_input(f'Shell thickness (tn) [{unit_len}]', value=10.0) vessel_type = st.selectbox('Vessel type', ['cylinder', 'cone', 'sphere']) P_internal = st.number_input(f'Design internal pressure [{unit_pres}]', value=0.5) P_external = st.number_input(f'Design external pressure (use negative for vacuum) [{unit_pres}]', value=0.0) S_allow = st.number_input('Allowable stress S [MPa]', value=138.0) with col2: st.subheader('Nozzle / Opening') d = st.number_input(f'Nozzle inside diameter (d) [{unit_len}]', value=150.0) tn_nozzle = st.number_input(f'Nozzle neck thickness [{unit_len}]', value=6.0) projection = st.number_input(f'Projection (length) [{unit_len}]', value=30.0) pad_yes = st.checkbox('Reinforcing pad present', value=False) pad_OD = None pad_t = None pad_weld_leg = 0.0 if pad_yes: pad_OD = st.number_input(f'Pad OD [{unit_len}]', value=250.0) pad_t = st.number_input(f'Pad thickness [{unit_len}]', value=8.0) pad_weld_leg = st.number_input('Pad weld leg length (effective) [mm or in]', value=4.0) split_pad = st.checkbox('Split pad (apply 0.75 multiplier to A5)', value=False) orientation = st.selectbox('Location / orientation', ['axial (longitudinal)', 'circumferential', 'oblique']) orientation_angle = None if orientation == 'oblique': orientation_angle = st.number_input('Orientation angle (deg)', min_value=0.0, max_value=90.0, value=45.0) # Unit conversions to internal SI mm/MPa for calculation convenience if units.startswith('Imperial'): Do_si = to_si_length(Do, 'in') d_si = to_si_length(d, 'in') tn_si = to_si_length(tn, 'in') tn_nozzle_si = to_si_length(tn_nozzle, 'in') pad_OD_si = to_si_length(pad_OD, 'in') if pad_OD is not None else None pad_t_si = to_si_length(pad_t, 'in') if pad_t is not None else None P_internal_si = to_si_pressure(P_internal, 'psi') P_external_si = to_si_pressure(P_external, 'psi') pad_weld_leg_si = to_si_length(pad_weld_leg, 'in') if pad_weld_leg is not None else 0.0 else: Do_si = Do d_si = d tn_si = tn tn_nozzle_si = tn_nozzle pad_OD_si = pad_OD pad_t_si = pad_t P_internal_si = P_internal P_external_si = P_external pad_weld_leg_si = pad_weld_leg # --------------------------- # Calculations # --------------------------- st.header('Calculations') if demo_mode: A_req_int = required_area_internal_demo(P_internal_si, d_si, S_allow, F=F_val) A_req_ext = required_area_external_demo(P_external_si, d_si, S_allow, F=F_val) if P_external_si != 0 else 0.0 else: A_req_int = None A_req_ext = None A1 = calc_A1(Do_si, tn_si, d_si) A2 = calc_A2_pad(d_si, pad_OD_si, pad_t_si) if pad_yes else 0.0 perimeter = math.pi * d_si A_weld = calc_A_welds(perimeter, pad_weld_leg_si, include_welds) # A5 placeholder: pad projection/tangential contribution A5 = A2 if split_pad: A5 = 0.75 * A5 A_avail = A1 + A2 + A_weld # Governing required area if A_req_ext and A_req_ext > A_req_int: A_req = A_req_ext governing = 'External pressure (UG-37(d))' else: A_req = A_req_int governing = 'Internal pressure (UG-37(c))' # --------------------------- # Results # --------------------------- st.subheader('Summary') colr1, colr2 = st.columns([1,2]) with colr1: if A_req is None: st.warning('Required-area formula not implemented. App is running in demo mode — results are NOT code-validated.') else: pass with colr2: st.markdown(f"**Governing case:** {governing}") st.markdown(f"**Required reinforcement area (A_req)**: {A_req:,.1f} mm²") st.markdown(f"**Available reinforcement area (A_avail)**: {A_avail:,.1f} mm²") margin = None if A_req and A_avail is not None: margin = A_avail - A_req pct = (margin / A_req * 100.0) if A_req != 0 else 0.0 if margin >= 0: st.success(f'PASS — Margin = {margin:,.1f} mm² ({pct:.1f}%)') else: st.error(f'FAIL — Shortfall = {abs(margin):,.1f} mm² ({pct:.1f}%)') # Detailed table st.subheader('Component breakdown (approximate / demo values)') comp = pd.DataFrame({ 'Component':['A1 - Shell material', 'A2 - Pad ring', 'A_weld - Weld contribution', 'A5 - Pad effective'], 'Area_mm2':[A1, A2, A_weld, A5] }) st.dataframe(comp) # Download results st.subheader('Export') buf = BytesIO() comp.to_csv(buf, index=False) buf.seek(0) st.download_button('Download components CSV', buf, file_name='ug37_components.csv', mime='text/csv') st.info('Reminder: This app is a skeleton/demo. Replace required-area functions with the official UG-37 formulas and verify against the ASME Code before using for design.')