Collection of engineering calculation projects (Python + Typst), each with input, calc script, tests, results, and generated PDF where available.
110 lines
5.5 KiB
Markdown
110 lines
5.5 KiB
Markdown
# 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()` reads `HERE/"input.yaml"` with yaml.safe_load
|
|
- `@pytest.fixture def result(): return compute(load_input())`
|
|
|
|
### Tests to implement (each as `def test_*` function)
|
|
|
|
1. `test_example_pressures_and_geometry(result)` — asserts:
|
|
- `Af_ft2 == 9.0` approx
|
|
- `Bf_ft == 3.0`, `Bf_in == 36.0`
|
|
- `d_in == 9.0`
|
|
- `q_psf == pytest.approx(2044.44, rel=0.01)` (18.4k/9) — allow 1% because Ps 18.4 vs derived 18.36
|
|
- `qu_psf == pytest.approx(2911.11, rel=0.01)` (26.2/9)
|
|
- `qa_psf == 2500.0`
|
|
|
|
2. `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)` (qu*Bf*L1)
|
|
- `Vc_one_way_kip == pytest.approx(35.45, rel=0.01)` (2*sqrt(3000)*36*9/1000)
|
|
- `phiVc_one_way_kip == pytest.approx(26.59, rel=0.01)`
|
|
- `checks["one_way_shear"]["ok"] is True`
|
|
- D/C approx 0.055
|
|
|
|
3. `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)` (vc*bo*d)
|
|
- `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.
|
|
|
|
4. `test_example_flexure(result)` — asserts:
|
|
- `Lc_in == pytest.approx(11.0)` ((36-14)/2)
|
|
- `Mu_kipft == pytest.approx(3.68, abs=0.1)` (qu*Bf*Lc^2/2)
|
|
- `a_in == pytest.approx(0.524, abs=0.02)` (As*fy/(0.85*fc*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 True`
|
|
- `checks["minimum_steel"]["ok"] is True` (rho >=0.0018)
|
|
|
|
5. `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.85*3000*36*2/1000)
|
|
- `phiBn_kip == pytest.approx(119.34, abs=0.5)`
|
|
- `checks["bearing"]["ok"] is True`
|
|
|
|
6. `test_all_checks_pass(result)` — asserts all six checks ok is True.
|
|
|
|
7. `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.
|
|
|
|
8. `test_wrong_dimension_is_rejected()` — sets `Bf: "3 kip"` and expects ValueError match "Bf".
|
|
|
|
9. `test_effective_depth_validation()` — sets cover "13 in" with Df "12 in" (d negative) expects ValueError.
|
|
|
|
10. `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
|
|
|
|
1. `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v` shows at least 10 tests, all passed.
|
|
2. Tests lock the corrected benchmark within stated tolerances; changing any equation in calc.py causes at least one failure.
|
|
3. Wrong-dimension and validation tests raise ValueError with correct field name.
|
|
4. Alternate units test demonstrates Pint equivalence.
|
|
5. 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.
|