Update app.py
Browse files
app.py
CHANGED
|
@@ -7,7 +7,7 @@ Single-file Streamlit app with:
|
|
| 7 |
- Material database (selectable)
|
| 8 |
- UG-27 shell thickness (circumferential & longitudinal checks)
|
| 9 |
- UG-32 head thickness (ellipsoidal / torispherical / hemispherical)
|
| 10 |
-
-
|
| 11 |
- PWHT recommendation (simplified, UCS-56 style)
|
| 12 |
- Impact test (Design MDMT vs Rated MDMT) approximation (UCS-66 style approximation)
|
| 13 |
- MAWP calculation from thickness
|
|
@@ -29,7 +29,7 @@ import pandas as pd
|
|
| 29 |
import streamlit as st
|
| 30 |
|
| 31 |
# Module metadata
|
| 32 |
-
__version__ = "0.1.
|
| 33 |
|
| 34 |
# ---------------------------
|
| 35 |
# Constants
|
|
@@ -37,7 +37,7 @@ __version__ = "0.1.0"
|
|
| 37 |
ATM_PRESSURE_PSI = 14.7 # used only for context
|
| 38 |
PIPE_THICKNESS_REDUCTION = 0.125 # 12.5% reduction for pipe grades
|
| 39 |
DEFAULT_CORROSION_ALLOWANCE_IN = 0.125
|
| 40 |
-
SUPPORTED_ASME_EDITIONS = ["2019", "2021", "custom"]
|
| 41 |
|
| 42 |
# Units formatting
|
| 43 |
INCH_DECIMALS = 4
|
|
@@ -339,64 +339,84 @@ def calculate_head_thickness(P: float, D: float, S: float, E: float, head_type:
|
|
| 339 |
}
|
| 340 |
|
| 341 |
|
| 342 |
-
def calculate_nozzle_reinforcement(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
"""
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
-
|
| 348 |
-
-
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
"""
|
| 351 |
-
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
-
A_required = d_opening * t_shell_required
|
| 355 |
-
|
| 356 |
-
# Approximate nozzle contribution: height limited to 2.5 * smaller thickness
|
| 357 |
-
height_limit = min(2.5 * t_shell_required, 2.5 * t_nozzle)
|
| 358 |
-
A_nozzle = t_nozzle * height_limit
|
| 359 |
-
|
| 360 |
-
# Pad contribution
|
| 361 |
-
A_pad = t_pad * pad_width if (t_pad and pad_width) else 0.0
|
| 362 |
-
|
| 363 |
-
# Shell area conservative: we count none (ASSUMPTION: conservative)
|
| 364 |
-
A_shell = 0.0
|
| 365 |
-
|
| 366 |
-
# Apply pipe grade thickness reduction for nozzle if material_grade indicates pipe
|
| 367 |
reduction_info = ""
|
|
|
|
| 368 |
if material_grade:
|
| 369 |
-
# find if given material is in data and pipe grade
|
| 370 |
for cat in MATERIALS:
|
| 371 |
mg = MATERIALS[cat].get(material_grade)
|
| 372 |
if mg:
|
| 373 |
if mg.get("is_pipe_grade"):
|
| 374 |
-
#
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
reduction_info = f"Applied {PIPE_THICKNESS_REDUCTION*100:.1f}% pipe thickness reduction to nozzle (effective nozzle thickness = {effective_nozzle_t:.4f})."
|
| 380 |
break
|
| 381 |
|
| 382 |
-
A_available =
|
|
|
|
| 383 |
adequate = A_available >= A_required
|
| 384 |
safety_factor = (A_available / A_required) if A_required > 0 else float("inf")
|
| 385 |
|
| 386 |
assumptions = [
|
| 387 |
-
"
|
| 388 |
-
"
|
| 389 |
-
"
|
| 390 |
-
"
|
|
|
|
| 391 |
]
|
| 392 |
if reduction_info:
|
| 393 |
assumptions.append(reduction_info)
|
| 394 |
|
| 395 |
return {
|
| 396 |
"area_required": float(A_required),
|
| 397 |
-
"
|
| 398 |
"area_nozzle": float(A_nozzle),
|
| 399 |
"area_pad": float(A_pad),
|
|
|
|
| 400 |
"area_available_total": float(A_available),
|
| 401 |
"reinforcement_adequate": bool(adequate),
|
| 402 |
"safety_factor": float(safety_factor),
|
|
@@ -442,14 +462,10 @@ def _approximate_rated_mdmt(material_group: str, thickness_in_in: float) -> floa
|
|
| 442 |
"""
|
| 443 |
APPROXIMATION: Provide a conservative approximate rated MDMT based on material group and thickness.
|
| 444 |
This is NOT the full UCS-66 lookup. Use as a placeholder to compare with Design MDMT.
|
| 445 |
-
We define a simple table for P1 and P8
|
| 446 |
-
- For P1 (carbon): thicker -> higher (warmer) rated MDMT threshold (conservative)
|
| 447 |
-
- For P8 (stainless): very low rated MDMT (stainless generally exempt)
|
| 448 |
-
Returns MDMT in °F.
|
| 449 |
"""
|
| 450 |
if material_group == "P8":
|
| 451 |
-
return -325.0
|
| 452 |
-
# P1 conservative regression-like mapping (values illustrative)
|
| 453 |
if thickness_in_in <= 0.5:
|
| 454 |
return 60.0
|
| 455 |
if thickness_in_in <= 1.0:
|
|
@@ -559,6 +575,9 @@ def init_session_state_defaults():
|
|
| 559 |
"use_reinforcing_pad": False,
|
| 560 |
"pad_thickness": 0.25,
|
| 561 |
"pad_width": 8.0,
|
|
|
|
|
|
|
|
|
|
| 562 |
"calc_atm_shell": False,
|
| 563 |
"asme_edition": SUPPORTED_ASME_EDITIONS[0],
|
| 564 |
}
|
|
@@ -627,8 +646,6 @@ def run_calculations(inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 627 |
# Optionally compute atmospheric shell
|
| 628 |
atm_shell = None
|
| 629 |
if inputs.get("calc_atm_shell"):
|
| 630 |
-
# For atmospheric only, P = 0 -> thickness tends to 0. Use minimal thickness assumption.
|
| 631 |
-
# ASSUMPTION: Provide thickness for vacuum containment? Here we just return a message.
|
| 632 |
atm_shell = {"note": "Atmospheric shell check requested: with P=0 this calculation is geometry-specific. Provide loading (wind, vacuum) for meaningful thickness check."}
|
| 633 |
|
| 634 |
# Head thickness
|
|
@@ -636,12 +653,17 @@ def run_calculations(inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 636 |
head_required = head["required_thickness"]
|
| 637 |
total_head = head_required + ca
|
| 638 |
|
| 639 |
-
# Nozzle reinforcement
|
| 640 |
nozzle = calculate_nozzle_reinforcement(
|
| 641 |
-
inputs["nozzle_opening_diameter"],
|
|
|
|
|
|
|
| 642 |
inputs["pad_thickness"] if inputs["use_reinforcing_pad"] else 0.0,
|
| 643 |
inputs["pad_width"] if inputs["use_reinforcing_pad"] else 0.0,
|
| 644 |
mat_grade if mat_cat != "User Defined" else None,
|
|
|
|
|
|
|
|
|
|
| 645 |
)
|
| 646 |
|
| 647 |
# For pipe grades, mention 12.5% reduction in shell effective thickness for reinforcement checks
|
|
@@ -652,10 +674,7 @@ def run_calculations(inputs: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 652 |
reduced_shell_effective = total_shell
|
| 653 |
|
| 654 |
# PWHT
|
| 655 |
-
# thickness for pwht in inches: if units SI, convert mm to inches (we assume inside_diameter units but thickness returned in same length units)
|
| 656 |
-
# ASSUMPTION: inputs thickness are in inches when unit_system == USC, mm when SI. For PWHT we need inches.
|
| 657 |
if unit_system == "SI":
|
| 658 |
-
# convert mm to inches: assume thickness in mm -> mm / 25.4
|
| 659 |
thickness_in_in = total_shell / 25.4
|
| 660 |
else:
|
| 661 |
thickness_in_in = total_shell
|
|
@@ -705,7 +724,7 @@ def main():
|
|
| 705 |
st.session_state["unit_system"] = unit_system
|
| 706 |
|
| 707 |
# ASME edition placeholder
|
| 708 |
-
st.session_state["asme_edition"] = st.sidebar.selectbox("ASME Section VIII Edition", SUPPORTED_ASME_EDITIONS, index=0)
|
| 709 |
|
| 710 |
# Basic design
|
| 711 |
pressure_label = "Design Pressure (psi)" if unit_system == "USC" else "Design Pressure (bar)"
|
|
@@ -722,7 +741,8 @@ def main():
|
|
| 722 |
st.sidebar.markdown("---")
|
| 723 |
st.session_state["inside_diameter"] = st.sidebar.number_input(length_label, value=float(st.session_state["inside_diameter"]))
|
| 724 |
st.session_state["head_type"] = st.sidebar.selectbox("Head Type", ["ellipsoidal", "torispherical", "hemispherical"], index=0 if st.session_state["head_type"] == "ellipsoidal" else 0)
|
| 725 |
-
|
|
|
|
| 726 |
|
| 727 |
st.sidebar.markdown("---")
|
| 728 |
st.session_state["material_category"] = st.sidebar.selectbox("Material Category", ["Carbon Steel", "Stainless Steel", "User Defined"], index=0)
|
|
@@ -734,7 +754,6 @@ def main():
|
|
| 734 |
st.session_state["user_defined_ce"] = st.sidebar.number_input("User-defined carbon equivalent (if known)", value=0.30)
|
| 735 |
else:
|
| 736 |
grades = list(MATERIALS[st.session_state["material_category"]].keys())
|
| 737 |
-
# select current grade if available
|
| 738 |
try:
|
| 739 |
idx = grades.index(st.session_state["material_grade"])
|
| 740 |
except Exception:
|
|
@@ -750,6 +769,12 @@ def main():
|
|
| 750 |
st.session_state["pad_thickness"] = st.sidebar.number_input("Pad Thickness (same unit)", value=float(st.session_state["pad_thickness"]))
|
| 751 |
st.session_state["pad_width"] = st.sidebar.number_input("Pad Width (same unit)", value=float(st.session_state["pad_width"]))
|
| 752 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
st.sidebar.markdown("---")
|
| 754 |
st.session_state["calc_atm_shell"] = st.sidebar.checkbox("Also calculate shell at atmospheric pressure", value=st.session_state["calc_atm_shell"])
|
| 755 |
|
|
@@ -782,6 +807,9 @@ def main():
|
|
| 782 |
"use_reinforcing_pad": bool(st.session_state["use_reinforcing_pad"]),
|
| 783 |
"pad_thickness": float(st.session_state.get("pad_thickness", 0.0)),
|
| 784 |
"pad_width": float(st.session_state.get("pad_width", 0.0)),
|
|
|
|
|
|
|
|
|
|
| 785 |
"calc_atm_shell": bool(st.session_state.get("calc_atm_shell", False)),
|
| 786 |
}
|
| 787 |
|
|
@@ -823,7 +851,6 @@ def main():
|
|
| 823 |
|
| 824 |
with st.expander("Show step-by-step shell calculations"):
|
| 825 |
st.code(f"Inputs: P={inputs['design_pressure']}, R={inputs['inside_diameter']/2.0}, S={get_allowable_stress(inputs['material_category'], inputs['material_grade'], inputs['design_temperature'], 'USC' if unit_system=='USC' else 'SI')[0]}, E={inputs['joint_efficiency']}")
|
| 826 |
-
# Show computed values numerically
|
| 827 |
st.write(shell)
|
| 828 |
else:
|
| 829 |
st.info("Press **Calculate** to run shell thickness calculations.")
|
|
@@ -843,23 +870,28 @@ def main():
|
|
| 843 |
st.info("Press **Calculate** to run head thickness calculations.")
|
| 844 |
|
| 845 |
with tab3:
|
| 846 |
-
st.header("Nozzle Reinforcement (UG-37 approx)")
|
|
|
|
| 847 |
if results:
|
| 848 |
nozzle = results["nozzle"]
|
| 849 |
units_len = "in" if unit_system == "USC" else "mm"
|
| 850 |
st.write("Assumptions:")
|
| 851 |
for a in nozzle["assumptions"]:
|
| 852 |
st.write("-", a)
|
| 853 |
-
st.write("Required area:", f"{format_value(nozzle['area_required'], units_len)} {units_len}^2")
|
| 854 |
-
st.write("
|
|
|
|
|
|
|
|
|
|
|
|
|
| 855 |
if nozzle["reinforcement_adequate"]:
|
| 856 |
st.success(f"Reinforcement adequate (SF = {nozzle['safety_factor']:.2f})")
|
| 857 |
else:
|
| 858 |
st.error(f"Reinforcement inadequate (SF = {nozzle['safety_factor']:.2f})")
|
| 859 |
-
with st.expander("Show step-by-step nozzle calculation"):
|
| 860 |
st.write(nozzle)
|
| 861 |
else:
|
| 862 |
-
st.info("
|
| 863 |
|
| 864 |
with tab4:
|
| 865 |
st.header("PWHT & Impact Test")
|
|
@@ -880,7 +912,6 @@ def main():
|
|
| 880 |
st.write("Suggested hold time:", pwht["hold_time"])
|
| 881 |
|
| 882 |
st.subheader("Impact Test (MDMT check)")
|
| 883 |
-
# show rated and design MDMT
|
| 884 |
rated = impact["rated_mdmt"]
|
| 885 |
design = impact["design_mdmt"]
|
| 886 |
if unit_system == "SI":
|
|
|
|
| 7 |
- Material database (selectable)
|
| 8 |
- UG-27 shell thickness (circumferential & longitudinal checks)
|
| 9 |
- UG-32 head thickness (ellipsoidal / torispherical / hemispherical)
|
| 10 |
+
- Improved UG-37-style nozzle reinforcement area method (conservative approx)
|
| 11 |
- PWHT recommendation (simplified, UCS-56 style)
|
| 12 |
- Impact test (Design MDMT vs Rated MDMT) approximation (UCS-66 style approximation)
|
| 13 |
- MAWP calculation from thickness
|
|
|
|
| 29 |
import streamlit as st
|
| 30 |
|
| 31 |
# Module metadata
|
| 32 |
+
__version__ = "0.1.1" # bumped for nozzle + edition + E updates
|
| 33 |
|
| 34 |
# ---------------------------
|
| 35 |
# Constants
|
|
|
|
| 37 |
ATM_PRESSURE_PSI = 14.7 # used only for context
|
| 38 |
PIPE_THICKNESS_REDUCTION = 0.125 # 12.5% reduction for pipe grades
|
| 39 |
DEFAULT_CORROSION_ALLOWANCE_IN = 0.125
|
| 40 |
+
SUPPORTED_ASME_EDITIONS = ["2019", "2021", "2025", "custom"] # added 2025
|
| 41 |
|
| 42 |
# Units formatting
|
| 43 |
INCH_DECIMALS = 4
|
|
|
|
| 339 |
}
|
| 340 |
|
| 341 |
|
| 342 |
+
def calculate_nozzle_reinforcement(
|
| 343 |
+
d_opening: float,
|
| 344 |
+
t_shell_required: float,
|
| 345 |
+
t_nozzle: float,
|
| 346 |
+
t_pad: float,
|
| 347 |
+
pad_width: float,
|
| 348 |
+
material_grade: Optional[str] = None,
|
| 349 |
+
shell_local_extra_thickness: float = 0.0,
|
| 350 |
+
weld_throat: float = 0.0,
|
| 351 |
+
weld_efficiency: float = 0.7,
|
| 352 |
+
) -> Dict[str, Any]:
|
| 353 |
"""
|
| 354 |
+
Improved simplified UG-37 area method approximation.
|
| 355 |
+
|
| 356 |
+
Method (conservative approximations):
|
| 357 |
+
- Required area: A_req = d_opening * t_shell_required
|
| 358 |
+
- Nozzle contribution: A_nozzle = pi * d_opening * t_nozzle (cylindrical projection)
|
| 359 |
+
# APPROXIMATION: Using circumference*thickness as projected reinforcement area.
|
| 360 |
+
- Pad contribution: A_pad = pad_width * t_pad * 2 (count both faces as conservative rectangular pads)
|
| 361 |
+
- Shell local extra thickness contribution: A_shell_local = pi * d_opening * shell_local_extra_thickness
|
| 362 |
+
- Weld contribution: A_weld = weld_throat * pi * d_opening * weld_efficiency
|
| 363 |
+
- For pipe grades (SA-106B, SA-53B-ERW, SA-312-*) apply 12.5% thickness reduction to nozzle/pad/weld contributions.
|
| 364 |
+
- Assumptions and limitations are returned in 'assumptions' and should be reviewed by an engineer.
|
| 365 |
"""
|
| 366 |
+
# Input validation
|
| 367 |
+
if d_opening <= 0:
|
| 368 |
+
raise ValueError("Nozzle opening diameter must be positive.")
|
| 369 |
+
if t_shell_required <= 0:
|
| 370 |
+
raise ValueError("Shell required thickness must be positive.")
|
| 371 |
+
if t_nozzle <= 0:
|
| 372 |
+
raise ValueError("Nozzle wall thickness must be positive.")
|
| 373 |
+
|
| 374 |
+
A_required = d_opening * t_shell_required # projection length * thickness
|
| 375 |
+
|
| 376 |
+
# Nozzle cylindrical contribution (projection)
|
| 377 |
+
A_nozzle = math.pi * d_opening * t_nozzle # circumference * thickness (approx)
|
| 378 |
+
# Pad contribution: two faces conservative rectangular pad
|
| 379 |
+
A_pad = pad_width * t_pad * 2.0 if (t_pad and pad_width) else 0.0
|
| 380 |
+
# Shell local extra thickness contribution (if user provides local reinforcement)
|
| 381 |
+
A_shell_local = math.pi * d_opening * shell_local_extra_thickness if shell_local_extra_thickness else 0.0
|
| 382 |
+
# Weld contribution
|
| 383 |
+
A_weld = weld_throat * math.pi * d_opening * weld_efficiency if weld_throat and weld_efficiency else 0.0
|
| 384 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
reduction_info = ""
|
| 386 |
+
# check pipe grade
|
| 387 |
if material_grade:
|
|
|
|
| 388 |
for cat in MATERIALS:
|
| 389 |
mg = MATERIALS[cat].get(material_grade)
|
| 390 |
if mg:
|
| 391 |
if mg.get("is_pipe_grade"):
|
| 392 |
+
# apply reduction to contributions that rely on nominal thickness (nozzle, pad, weld)
|
| 393 |
+
A_nozzle = A_nozzle * (1.0 - PIPE_THICKNESS_REDUCTION)
|
| 394 |
+
A_pad = A_pad * (1.0 - PIPE_THICKNESS_REDUCTION)
|
| 395 |
+
A_weld = A_weld * (1.0 - PIPE_THICKNESS_REDUCTION)
|
| 396 |
+
reduction_info = f"Applied {PIPE_THICKNESS_REDUCTION*100:.1f}% pipe thickness reduction to nozzle/pad/weld contributions."
|
|
|
|
| 397 |
break
|
| 398 |
|
| 399 |
+
A_available = A_shell_local + A_nozzle + A_pad + A_weld
|
| 400 |
+
|
| 401 |
adequate = A_available >= A_required
|
| 402 |
safety_factor = (A_available / A_required) if A_required > 0 else float("inf")
|
| 403 |
|
| 404 |
assumptions = [
|
| 405 |
+
"# ASSUMPTION: Using projection-area approximations (circumference*thickness) for nozzle contribution.",
|
| 406 |
+
"# APPROXIMATION: Pad is modeled as rectangular and both faces are counted for conservatism.",
|
| 407 |
+
"# ASSUMPTION: Shell local extra thickness is approximated as circumference * extra_thickness.",
|
| 408 |
+
"# ASSUMPTION: Weld contribution approximated as weld_throat * circumference * efficiency.",
|
| 409 |
+
"Real UG-37 uses more exact geometric projections and weld strength contribution; this is conservative and simplified.",
|
| 410 |
]
|
| 411 |
if reduction_info:
|
| 412 |
assumptions.append(reduction_info)
|
| 413 |
|
| 414 |
return {
|
| 415 |
"area_required": float(A_required),
|
| 416 |
+
"area_shell_local": float(A_shell_local),
|
| 417 |
"area_nozzle": float(A_nozzle),
|
| 418 |
"area_pad": float(A_pad),
|
| 419 |
+
"area_weld": float(A_weld),
|
| 420 |
"area_available_total": float(A_available),
|
| 421 |
"reinforcement_adequate": bool(adequate),
|
| 422 |
"safety_factor": float(safety_factor),
|
|
|
|
| 462 |
"""
|
| 463 |
APPROXIMATION: Provide a conservative approximate rated MDMT based on material group and thickness.
|
| 464 |
This is NOT the full UCS-66 lookup. Use as a placeholder to compare with Design MDMT.
|
| 465 |
+
We define a simple table for P1 and P8 (°F).
|
|
|
|
|
|
|
|
|
|
| 466 |
"""
|
| 467 |
if material_group == "P8":
|
| 468 |
+
return -325.0
|
|
|
|
| 469 |
if thickness_in_in <= 0.5:
|
| 470 |
return 60.0
|
| 471 |
if thickness_in_in <= 1.0:
|
|
|
|
| 575 |
"use_reinforcing_pad": False,
|
| 576 |
"pad_thickness": 0.25,
|
| 577 |
"pad_width": 8.0,
|
| 578 |
+
"shell_local_extra_thickness": 0.0,
|
| 579 |
+
"weld_throat": 0.1,
|
| 580 |
+
"weld_efficiency": 0.7,
|
| 581 |
"calc_atm_shell": False,
|
| 582 |
"asme_edition": SUPPORTED_ASME_EDITIONS[0],
|
| 583 |
}
|
|
|
|
| 646 |
# Optionally compute atmospheric shell
|
| 647 |
atm_shell = None
|
| 648 |
if inputs.get("calc_atm_shell"):
|
|
|
|
|
|
|
| 649 |
atm_shell = {"note": "Atmospheric shell check requested: with P=0 this calculation is geometry-specific. Provide loading (wind, vacuum) for meaningful thickness check."}
|
| 650 |
|
| 651 |
# Head thickness
|
|
|
|
| 653 |
head_required = head["required_thickness"]
|
| 654 |
total_head = head_required + ca
|
| 655 |
|
| 656 |
+
# Nozzle reinforcement (improved)
|
| 657 |
nozzle = calculate_nozzle_reinforcement(
|
| 658 |
+
inputs["nozzle_opening_diameter"],
|
| 659 |
+
total_shell,
|
| 660 |
+
inputs["nozzle_wall_thickness"],
|
| 661 |
inputs["pad_thickness"] if inputs["use_reinforcing_pad"] else 0.0,
|
| 662 |
inputs["pad_width"] if inputs["use_reinforcing_pad"] else 0.0,
|
| 663 |
mat_grade if mat_cat != "User Defined" else None,
|
| 664 |
+
shell_local_extra_thickness=inputs.get("shell_local_extra_thickness", 0.0),
|
| 665 |
+
weld_throat=inputs.get("weld_throat", 0.0),
|
| 666 |
+
weld_efficiency=inputs.get("weld_efficiency", 0.7),
|
| 667 |
)
|
| 668 |
|
| 669 |
# For pipe grades, mention 12.5% reduction in shell effective thickness for reinforcement checks
|
|
|
|
| 674 |
reduced_shell_effective = total_shell
|
| 675 |
|
| 676 |
# PWHT
|
|
|
|
|
|
|
| 677 |
if unit_system == "SI":
|
|
|
|
| 678 |
thickness_in_in = total_shell / 25.4
|
| 679 |
else:
|
| 680 |
thickness_in_in = total_shell
|
|
|
|
| 724 |
st.session_state["unit_system"] = unit_system
|
| 725 |
|
| 726 |
# ASME edition placeholder
|
| 727 |
+
st.session_state["asme_edition"] = st.sidebar.selectbox("ASME Section VIII Edition", SUPPORTED_ASME_EDITIONS, index=SUPPORTED_ASME_EDITIONS.index(st.session_state.get("asme_edition", SUPPORTED_ASME_EDITIONS[0])))
|
| 728 |
|
| 729 |
# Basic design
|
| 730 |
pressure_label = "Design Pressure (psi)" if unit_system == "USC" else "Design Pressure (bar)"
|
|
|
|
| 741 |
st.sidebar.markdown("---")
|
| 742 |
st.session_state["inside_diameter"] = st.sidebar.number_input(length_label, value=float(st.session_state["inside_diameter"]))
|
| 743 |
st.session_state["head_type"] = st.sidebar.selectbox("Head Type", ["ellipsoidal", "torispherical", "hemispherical"], index=0 if st.session_state["head_type"] == "ellipsoidal" else 0)
|
| 744 |
+
# Added E = 0.7 option as requested
|
| 745 |
+
st.session_state["joint_efficiency"] = st.sidebar.selectbox("Joint Efficiency (E)", [1.0, 0.95, 0.9, 0.85, 0.7], index=[1.0,0.95,0.9,0.85,0.7].index(st.session_state.get("joint_efficiency", 1.0)))
|
| 746 |
|
| 747 |
st.sidebar.markdown("---")
|
| 748 |
st.session_state["material_category"] = st.sidebar.selectbox("Material Category", ["Carbon Steel", "Stainless Steel", "User Defined"], index=0)
|
|
|
|
| 754 |
st.session_state["user_defined_ce"] = st.sidebar.number_input("User-defined carbon equivalent (if known)", value=0.30)
|
| 755 |
else:
|
| 756 |
grades = list(MATERIALS[st.session_state["material_category"]].keys())
|
|
|
|
| 757 |
try:
|
| 758 |
idx = grades.index(st.session_state["material_grade"])
|
| 759 |
except Exception:
|
|
|
|
| 769 |
st.session_state["pad_thickness"] = st.sidebar.number_input("Pad Thickness (same unit)", value=float(st.session_state["pad_thickness"]))
|
| 770 |
st.session_state["pad_width"] = st.sidebar.number_input("Pad Width (same unit)", value=float(st.session_state["pad_width"]))
|
| 771 |
|
| 772 |
+
# New inputs for improved nozzle reinforcement
|
| 773 |
+
st.sidebar.markdown("---")
|
| 774 |
+
st.session_state["shell_local_extra_thickness"] = st.sidebar.number_input("Local shell extra thickness (for reinforcement) (same unit)", value=float(st.session_state.get("shell_local_extra_thickness", 0.0)))
|
| 775 |
+
st.session_state["weld_throat"] = st.sidebar.number_input("Effective weld throat (same unit) for weld contribution", value=float(st.session_state.get("weld_throat", 0.1)))
|
| 776 |
+
st.session_state["weld_efficiency"] = st.sidebar.slider("Weld efficiency factor", min_value=0.0, max_value=1.0, value=float(st.session_state.get("weld_efficiency", 0.7)), step=0.05)
|
| 777 |
+
|
| 778 |
st.sidebar.markdown("---")
|
| 779 |
st.session_state["calc_atm_shell"] = st.sidebar.checkbox("Also calculate shell at atmospheric pressure", value=st.session_state["calc_atm_shell"])
|
| 780 |
|
|
|
|
| 807 |
"use_reinforcing_pad": bool(st.session_state["use_reinforcing_pad"]),
|
| 808 |
"pad_thickness": float(st.session_state.get("pad_thickness", 0.0)),
|
| 809 |
"pad_width": float(st.session_state.get("pad_width", 0.0)),
|
| 810 |
+
"shell_local_extra_thickness": float(st.session_state.get("shell_local_extra_thickness", 0.0)),
|
| 811 |
+
"weld_throat": float(st.session_state.get("weld_throat", 0.1)),
|
| 812 |
+
"weld_efficiency": float(st.session_state.get("weld_efficiency", 0.7)),
|
| 813 |
"calc_atm_shell": bool(st.session_state.get("calc_atm_shell", False)),
|
| 814 |
}
|
| 815 |
|
|
|
|
| 851 |
|
| 852 |
with st.expander("Show step-by-step shell calculations"):
|
| 853 |
st.code(f"Inputs: P={inputs['design_pressure']}, R={inputs['inside_diameter']/2.0}, S={get_allowable_stress(inputs['material_category'], inputs['material_grade'], inputs['design_temperature'], 'USC' if unit_system=='USC' else 'SI')[0]}, E={inputs['joint_efficiency']}")
|
|
|
|
| 854 |
st.write(shell)
|
| 855 |
else:
|
| 856 |
st.info("Press **Calculate** to run shell thickness calculations.")
|
|
|
|
| 870 |
st.info("Press **Calculate** to run head thickness calculations.")
|
| 871 |
|
| 872 |
with tab3:
|
| 873 |
+
st.header("Nozzle Reinforcement (UG-37 approx - improved)")
|
| 874 |
+
st.info("Provide nozzle geometry inputs below. The method is a conservative projection-area approximation; see assumptions in the result.")
|
| 875 |
if results:
|
| 876 |
nozzle = results["nozzle"]
|
| 877 |
units_len = "in" if unit_system == "USC" else "mm"
|
| 878 |
st.write("Assumptions:")
|
| 879 |
for a in nozzle["assumptions"]:
|
| 880 |
st.write("-", a)
|
| 881 |
+
st.write("Required area (A_required = d_opening * t_shell):", f"{format_value(nozzle['area_required'], units_len)} {units_len}^2")
|
| 882 |
+
st.write("Shell local contribution (A_shell_local):", f"{format_value(nozzle['area_shell_local'], units_len)} {units_len}^2")
|
| 883 |
+
st.write("Nozzle contribution (A_nozzle = pi*d*t_nozzle):", f"{format_value(nozzle['area_nozzle'], units_len)} {units_len}^2")
|
| 884 |
+
st.write("Pad contribution (A_pad):", f"{format_value(nozzle['area_pad'], units_len)} {units_len}^2")
|
| 885 |
+
st.write("Weld contribution (A_weld):", f"{format_value(nozzle['area_weld'], units_len)} {units_len}^2")
|
| 886 |
+
st.write("Total available area:", f"{format_value(nozzle['area_available_total'], units_len)} {units_len}^2")
|
| 887 |
if nozzle["reinforcement_adequate"]:
|
| 888 |
st.success(f"Reinforcement adequate (SF = {nozzle['safety_factor']:.2f})")
|
| 889 |
else:
|
| 890 |
st.error(f"Reinforcement inadequate (SF = {nozzle['safety_factor']:.2f})")
|
| 891 |
+
with st.expander("Show step-by-step nozzle calculation (raw values)"):
|
| 892 |
st.write(nozzle)
|
| 893 |
else:
|
| 894 |
+
st.info("Enter inputs and press **Calculate** to run nozzle reinforcement check.")
|
| 895 |
|
| 896 |
with tab4:
|
| 897 |
st.header("PWHT & Impact Test")
|
|
|
|
| 912 |
st.write("Suggested hold time:", pwht["hold_time"])
|
| 913 |
|
| 914 |
st.subheader("Impact Test (MDMT check)")
|
|
|
|
| 915 |
rated = impact["rated_mdmt"]
|
| 916 |
design = impact["design_mdmt"]
|
| 917 |
if unit_system == "SI":
|