Collection of engineering calculation projects (Python + Typst), each with input, calc script, tests, results, and generated PDF where available.
5.5 KiB
Task 002 — Numerical lock: test_concentric_footing.py (compute() only)
Goal
Lock the corrected Blavatnik benchmark in pytest so the calculator cannot drift, and guard Pint units and applicability limits.
Background
Depends on Task 001 contract. This task is purely numerical: it imports calc.py:compute and asserts pytest.approx on every value name. It does not require Typst. The reference PDF contains known inconsistencies (68in vs 92in punching perimeter, plate vs column area, 66in textual dimension vs 36in computed Af); tests lock the ACI-correct values documented in PROJECT_STATE.md.
All assertions target values produced by calc.py:compute() sub-blocks: Pressures, One-way shear, Two-way shear, Flexure, Concrete bearing.
Files to Modify
calcs/concentric-footing/test_concentric_footing.py— create.calcs/concentric-footing/calc.py— read-only, do not edit.calcs/concentric-footing/input.yaml— read-only.calcs/concentric-footing/results.json— read-only.
Implementation
Create calcs/concentric-footing/test_concentric_footing.py following calcs/wood-joist/test_wood_joist.py structure:
- Imports: pathlib Path, importlib.util, json, subprocess, sys, pytest, yaml
- Load calc module via
importlib.util.spec_from_file_location("concentric_footing_calc", HERE/"calc.py");compute = calc_module.compute - Helper
load_input()readsHERE/"input.yaml"with yaml.safe_load @pytest.fixture def result(): return compute(load_input())
Tests to implement (each as def test_* function)
-
test_example_pressures_and_geometry(result)— asserts:Af_ft2 == 9.0approxBf_ft == 3.0,Bf_in == 36.0d_in == 9.0q_psf == pytest.approx(2044.44, rel=0.01)(18.4k/9) — allow 1% because Ps 18.4 vs derived 18.36qu_psf == pytest.approx(2911.11, rel=0.01)(26.2/9)qa_psf == 2500.0
-
test_example_one_way_shear(result)— asserts:L1_in == pytest.approx(2.0, abs=0.01)((36-14)/2 -9 =2)Vu_one_way_kip == pytest.approx(1.455, abs=0.05)(quBfL1)Vc_one_way_kip == pytest.approx(35.45, rel=0.01)(2*sqrt(3000)369/1000)phiVc_one_way_kip == pytest.approx(26.59, rel=0.01)checks["one_way_shear"]["ok"] is True- D/C approx 0.055
-
test_example_two_way_shear(result)— asserts corrected ACI values:bo_in == pytest.approx(92.0)(4*(14+9))vc_psi == pytest.approx(219.089, rel=1e-3)(4*sqrt(3000))Vc_two_way_kip == pytest.approx(181.4, rel=0.02)(vcbod)phiVn_two_way_kip == pytest.approx(136.0, rel=0.02)Vu_two_way_kip == pytest.approx(15.51, abs=0.1)(qu*(9 - (23/12)^2))checks["two_way_shear"]["ok"] is True- Document that reference PDF reports bo 68in and Vc 134kip; this test locks ACI-correct 92in.
-
test_example_flexure(result)— asserts:Lc_in == pytest.approx(11.0)((36-14)/2)Mu_kipft == pytest.approx(3.68, abs=0.1)(quBfLc^2/2)a_in == pytest.approx(0.524, abs=0.02)(Asfy/(0.85fc*B))As_in2 == pytest.approx(0.785, abs=0.02)(4*#4 -> 4*0.196=0.785; reference rounds to 0.8, accept both with rel 0.03 but assert within 0.785±0.02)Mn_kipft == pytest.approx(34.27, abs=0.5)phiMn_kipft == pytest.approx(30.84, abs=0.5)rho == pytest.approx(0.00242, rel=0.02)checks["flexure"]["ok"] is Truechecks["minimum_steel"]["ok"] is True(rho >=0.0018)
-
test_example_bearing(result)— asserts:A1_in2 == pytest.approx(36.0)(6*6)A2_in2 == pytest.approx(1296.0)(36*36)sqrt_ratio == pytest.approx(6.0, abs=0.01)capped at 2 for Bn actually but store raw ratio and report capped separately? Store raw 6.0 and separately use capped 2 for Bn; test asserts stored sqrt_ratio is 6.0 and Bn uses capped 2.Bn_kip == pytest.approx(183.6, abs=0.5)(0.85300036*2/1000)phiBn_kip == pytest.approx(119.34, abs=0.5)checks["bearing"]["ok"] is True
-
test_all_checks_pass(result)— asserts all six checks ok is True. -
test_alternate_units_match_default(result)— copies input, replaces Bf with "36 in", fc with "3 ksi", fy with "60000 psi", Ps with "18400 lbf", Pu with "26200 lbf" etc, computes converted, asserts for keys ("Af_ft2","q_psf","Mu_kipft","Vu_one_way_kip","phiBn_kip") approx equal rel 1e-6. -
test_wrong_dimension_is_rejected()— setsBf: "3 kip"and expects ValueError match "Bf". -
test_effective_depth_validation()— sets cover "13 in" with Df "12 in" (d negative) expects ValueError. -
test_zero_footing_size_rejected()— sets Bf "0 ft" expects ValueError.
If any additional guard is implemented (lambda >1, N zero) add matching tests, but at minimum the 10 above.
Each test that reads values should use v = result["values"] and pytest.approx. The file must be runnable with python -m pytest calcs/concentric-footing/test_concentric_footing.py -v.
Acceptance Criteria
python -m pytest calcs/concentric-footing/test_concentric_footing.py -vshows at least 10 tests, all passed.- Tests lock the corrected benchmark within stated tolerances; changing any equation in calc.py causes at least one failure.
- Wrong-dimension and validation tests raise ValueError with correct field name.
- Alternate units test demonstrates Pint equivalence.
- No modification to
calc.py,input.yaml,results.json.
Tests
Builder runs:
python -m pip install -r requirements.txt
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
Expected: 10 passed (or more if extra guards).
Dependencies
Task 001 must be DONE.