calcs/concentric-footing/test_concentric_footing.py

170 lines
6.4 KiB
Python
Raw Normal View History

from pathlib import Path
import importlib.util
import json
import subprocess
import sys
import pytest
import yaml
HERE = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("concentric_footing_calc", HERE / "calc.py")
assert spec is not None and spec.loader is not None
calc_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(calc_module)
compute = calc_module.compute
def load_input():
with (HERE / "input.yaml").open(encoding="utf-8") as handle:
return yaml.safe_load(handle)
@pytest.fixture
def result():
return compute(load_input())
def test_example_pressures_and_geometry(result):
v = result["values"]
assert v["Af_ft2"] == pytest.approx(9.0)
assert v["Bf_ft"] == pytest.approx(3.0)
assert v["Bf_in"] == pytest.approx(36.0)
assert v["d_in"] == pytest.approx(9.0)
assert v["q_psf"] == pytest.approx(2044.44, rel=0.01)
assert v["qu_psf"] == pytest.approx(2911.11, rel=0.01)
assert v["qa_psf"] == 2500.0
def test_example_one_way_shear(result):
v = result["values"]
assert v["L1_in"] == pytest.approx(2.0, abs=0.01)
assert v["Vu_one_way_kip"] == pytest.approx(1.455, abs=0.05)
assert v["Vc_one_way_kip"] == pytest.approx(35.45, rel=0.01)
assert v["phiVc_one_way_kip"] == pytest.approx(26.59, rel=0.01)
assert result["checks"]["one_way_shear"]["ok"] is True
dc = v["Vu_one_way_kip"] / v["phiVc_one_way_kip"]
assert dc == pytest.approx(0.055, abs=0.001)
def test_example_two_way_shear(result):
v = result["values"]
# Reference PDF used 68 in; ACI-correct is 92 in (4*(c+d) for interior square column).
# PDF's 68 in perimeter yields ~134 kip Vc; ACI-correct 92 in yields ~181.4 kip Vc (phiVn ~136 kip).
assert v["bo_in"] == pytest.approx(92.0)
assert v["vc_psi"] == pytest.approx(219.089, rel=1e-3)
assert v["Vc_two_way_kip"] == pytest.approx(181.4, rel=0.02)
assert v["phiVn_two_way_kip"] == pytest.approx(136.0, rel=0.02)
assert v["Vu_two_way_kip"] == pytest.approx(15.51, abs=0.1)
assert result["checks"]["two_way_shear"]["ok"] is True
dc = v["Vu_two_way_kip"] / v["phiVn_two_way_kip"]
assert dc == pytest.approx(0.114, abs=0.002)
def test_example_flexure(result):
v = result["values"]
assert v["Lc_in"] == pytest.approx(11.0)
assert v["Mu_kipft"] == pytest.approx(3.68, abs=0.1)
assert v["a_in"] == pytest.approx(0.524, abs=0.02)
assert v["As_in2"] == pytest.approx(0.785, abs=0.02)
assert v["Mn_kipft"] == pytest.approx(34.27, abs=0.5)
assert v["phiMn_kipft"] == pytest.approx(30.84, abs=0.5)
assert v["rho"] == pytest.approx(0.00242, rel=0.02)
assert result["checks"]["flexure"]["ok"] is True
assert result["checks"]["minimum_steel"]["ok"] is True
dc = v["Mu_kipft"] / v["phiMn_kipft"]
assert dc == pytest.approx(0.12, abs=0.01)
def test_example_bearing(result):
v = result["values"]
assert v["A1_in2"] == pytest.approx(36.0)
assert v["A2_in2"] == pytest.approx(1296.0)
# Raw ratio stored; Bn uses capped 2.0.
assert v["sqrt_ratio"] == pytest.approx(6.0, abs=0.01)
assert v["Bn_kip"] == pytest.approx(183.6, abs=0.5)
assert v["phiBn_kip"] == pytest.approx(119.34, abs=0.5)
assert result["checks"]["bearing"]["ok"] is True
dc = v["Pu_kip"] / v["phiBn_kip"]
assert dc == pytest.approx(0.22, abs=0.01)
def test_all_checks_pass(result):
for k in ("soil_bearing", "one_way_shear", "two_way_shear", "flexure", "minimum_steel", "bearing"):
assert result["checks"][k]["ok"] is True
def test_alternate_units_match_default(result):
alt = load_input()
alt.update({
"Bf": "36 in",
"fc": "3 ksi",
"fy": "60000 psi",
"Ps": "18400 lbf",
"Pu": "26200 lbf",
})
converted = compute(alt)
for key in ("Af_ft2", "q_psf", "Mu_kipft", "Vu_one_way_kip", "phiBn_kip"):
assert converted["values"][key] == pytest.approx(result["values"][key], rel=1e-6)
def test_wrong_dimension_is_rejected():
bad = load_input()
bad["Bf"] = "3 kip"
with pytest.raises(ValueError, match="Bf"):
compute(bad)
def test_effective_depth_validation():
bad = load_input()
bad["cover"] = "13 in"
bad["Df"] = "12 in"
with pytest.raises(ValueError, match="d"):
compute(bad)
def test_zero_footing_size_rejected():
bad = load_input()
bad["Bf"] = "0 ft"
with pytest.raises(ValueError, match="Bf"):
compute(bad)
def test_typst_compiles_and_presents_python_numbers(result):
subprocess.run([sys.executable, str(HERE / "calc.py")], check=True, cwd=HERE.parents[1])
pdf = HERE / "generated" / "footing.pdf"
pdf.parent.mkdir(parents=True, exist_ok=True)
subprocess.run(
["typst", "compile", "--root", ".", "calcs/concentric-footing/footing.typ", str(pdf)],
check=True, cwd=HERE.parents[1],
)
assert pdf.exists()
query = subprocess.run(
["typst", "eval", "query(<concentric-footing-results>)", "--root", ".",
"--in", "calcs/concentric-footing/footing.typ", "--format", "json"],
check=True, capture_output=True, text=True, cwd=HERE.parents[1],
)
published = json.loads(query.stdout)
assert len(published) == 1
meta = published[0]["value"]
values = result["values"]
assert meta["soil_util"] == pytest.approx(values["q_psf"] / values["qa_psf"], abs=0.001)
assert meta["one_way_util"] == pytest.approx(values["Vu_one_way_kip"] / values["phiVc_one_way_kip"], abs=0.001)
assert meta["two_way_util"] == pytest.approx(values["Vu_two_way_kip"] / values["phiVn_two_way_kip"], abs=0.001)
assert meta["flexure_util"] == pytest.approx(values["Mu_kipft"] / values["phiMn_kipft"], abs=0.001)
assert meta["bearing_util"] == pytest.approx(values["Pu_kip"] / values["phiBn_kip"], abs=0.001)
def test_typst_load_demands_match_checked_inputs(result):
subprocess.run([sys.executable, str(HERE / "calc.py")], check=True, cwd=HERE.parents[1])
query = subprocess.run(
["typst", "eval", "query(<concentric-footing-loads>)", "--root", ".",
"--in", "calcs/concentric-footing/footing.typ", "--format", "json"],
check=True, capture_output=True, text=True, cwd=HERE.parents[1],
)
published = json.loads(query.stdout)
assert len(published) == 1
derived = published[0]["value"]
values = result["values"]
assert derived["Ps_kip"] == pytest.approx(values["Ps_kip"], abs=0.1)
assert derived["Pu_kip"] == pytest.approx(values["Pu_kip"], abs=0.1)