calcs/concentric-footing/PROJECT_STATE.md

211 lines
13 KiB
Markdown
Raw Permalink Normal View History

# Project State: Concentric Footing Analysis
Last updated: 2026-08-21
Project root: `calcs/concentric-footing/`
Parent worksheets root: `/home/smill/Sync/worksheets`
Reference: `CONCENTRIC-FOOTING.pdf` — Blavatnik concentric footing for steel column (square footing, ACI-based checks). Parent project conventions as documented in `worksheets/codemap.md` and `calcs/wood-joist/PROJECT_STATE.md`.
## Overview
New hybrid (Typst + Python) calculation at `calcs/concentric-footing/` that checks a square, concentrically loaded, reinforced concrete spread footing under combined service and ultimate axial load per **ACI 318-19**. Five checks are covered:
1. Soil bearing (service, ASD) — `q = Ps/Af <= qa`
2. One-way (beam) shear — ACI 22.5 — `Vc = 2*lambda*sqrt(f'c)*Bf*d`
3. Two-way (punching) shear — ACI 22.6 — `vc = min(4, 2+4/beta, 2+alpha_s*d/bo)*lambda*sqrt(f'c)`
4. Flexure (bending) — ACI 22.5/7 — Whitney block `a = As*fy/(0.85*f'c*Bf)`, `Mn = As*fy*(d-a/2)`
5. Concrete bearing on footing — ACI 22.8 — `Bn = 0.85*f'c*A1*sqrt(A2/A1) <= 2*0.85*f'c*A1`
Minimum reinforcement `rho = As/(Bf*d) >= 0.0018` is checked as `minimum_steel`.
The default `input.yaml` reproduces the Blavatnik reference example subject to documented corrections (bearing plate clarification and ACI-correct punching perimeter). The pytest suite locks the corrected numbers.
## Architecture decisions
- **Hybrid pattern** (same as `wood-joist` / `steel-beam`): `input.yaml` (Pint unit-bearing quantities, quoted strings) -> `calc.py` (`compute()` -> writes `results.json`) -> `footing.typ` (presents only, no recomputation) -> compiled PDF with `--root .` from `worksheets/`.
- **`results.json` shape** (unchanged contract): `{tool, version, project, prepared_by, values, checks}` with `tool = "concentric_footing"`, `version = "0.1"`.
- **Pint units**: all dimensional inputs are quoted strings (e.g. `"3000 psi"`, `"3 ft"`, `"18.4 kip"`). `calc.py` converts with a `quantity(value, unit, name)` helper identical to `wood-joist/calc.py`; dimensionless factors are plain floats.
- **`calc.py` CLI** mirrors `wood-joist`/`steel-beam`: `--input`, `--output`, `--stdout`; runnable as `python calcs/concentric-footing/calc.py` with no args.
- **ACI 318-19** is the governing standard and edition. All clause references are to ACI 318-19 Chapter 22 / 13.
- **`footing.typ` imports** from `../../lib/sheet.typ` and reads `results.json`. It also **derives loads in Typst** (mirroring `wood-joist`/`steel-beam`): `DLr`, `LLr`, `Br`, `Lr`, `Ar`, column weight `Wc = bc*bc*Lc*gamma_c` -> `Ps_derived = (DLr+LLr)*Ar + Wc`, `Pu_derived = 1.2*(DLr*Ar+Wc)+1.6*LLr*Ar`. These are emitted as `<concentric-footing-loads>` metadata and reconciled to the Python-checked `Ps`/`Pu` by the test suite. Capacity numbers are never recomputed in Typst.
- **Sheet helpers**: flexure, shear, bearing, and soil bearing use `check` (Demand/Capacity D/C). No `check_service` variant is needed; soil bearing is presented as `q` vs `qa`.
- **Compilation**: `typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf` so the shared logo at `assets/logo.png` resolves.
## Engineering decisions (pinned for the builder)
All equations, units, and applicability limits are pinned here. The builder must not invent behavior. Tolerances and benchmark values are under "Reference example".
### Inputs and units
Pint-parsed quantities (all positive, `ValueError` if <=0 or wrong dimension):
- `Ps` -> kip (service axial load, column + roof)
- `Pu` -> kip (factored axial load, 1.2D+1.6L)
- `qa` -> psf (allowable soil bearing, gross)
- `Bf` -> ft or in (square footing side; `Af = Bf^2` -> ft2, also `Bf_in = Bf_ft*12`)
- `Df` -> in (total footing thickness)
- `cover` -> in (to centroid of steel, so `d = Df - cover`; `d` is effective depth)
- `fc` -> psi or ksi (concrete `f'c`)
- `fy` -> psi or ksi (reinforcement yield)
- `lambda` -> float (lightweight factor, 1.0 normal weight, (0,1])
- `column_width` (`c`) -> in (square column side)
- `base_plate_width` (`bp`) -> in (square base plate side, `A1 = bp^2`; if omitted defaults to `c`)
- `rebar_size` -> int (e.g. 4 means #4 -> db = rebar_size/8 in)
- `N` -> int (number of bars per direction)
Dimensionless / integers are validated: `N` integer >=1, `rebar_size` integer 3..18, `lambda` in (0,1], `rho_min` hardcoded 0.0018.
Derived:
- `db_in = rebar_size/8`
- `As1_in2 = pi*db^2/4`
- `As_in2 = N*As1`
- `d_in = Df_in - cover_in` (cover to centroid per reference; `ValueError` if `d <=0` or `d > Df`)
- `Af_ft2 = Bf_ft^2`, `Af_in2 = Af_ft2*144`
- `A1_in2 = bp_in^2`, `A2_in2 = Af_in2`, `A2_ft2 = Af_ft2`
- `Bf_in = Bf_ft*12`, `L_cant_ft = (Bf_in - c_in)/2/12`, `L_cant_in = (Bf_in - c_in)/2`
### Check 1 — Soil bearing (service)
- `q_psf = Ps_lbf / Af_ft2` where `Ps_lbf = Ps_kip*1000`
- `qu_psf = Pu_lbf / Af_ft2` (ultimate pressure for concrete checks)
- `ok_soil = q_psf <= qa_psf`
- Report `q_psf`, `qu_psf`, `qa_psf`, `Af_ft2`.
- Note: footing self weight and soil surcharge are excluded (gross pressure follows reference). Scope note states this limitation.
### Check 2 — One-way (beam) shear — ACI 22.5.5, phi=0.75
- Critical section at distance `d` from column face.
- `L1_in = (Bf_in - c_in)/2 - d_in` (cantilever beyond section). If `L1_in <=0` then `Vu_kip = 0` (no shear beyond section).
- Otherwise `Vu_lbf = qu_psf * (Bf_ft) * (L1_in/12)` because `qu` (psf) * width (ft) * length (ft). So `Vu_kip = Vu_lbf/1000`.
- `Vc_lbf = 2*lambda*sqrt(fc_psi)*Bf_in*d_in` (ACI 22.5.5.1, `lambda` factor). `Vc_kip = Vc_lbf/1000`.
- `phiVc_kip = 0.75*Vc_kip`
- `ok_one_way = Vu_kip <= phiVc_kip`
- Also report `Vu/phiVc`.
### Check 3 — Two-way (punching) shear — ACI 22.6.5, phi=0.75
- `bo_in = 4*(c_in + d_in)` (interior square column; critical perimeter at d/2). Documented correction: reference shows 68in which is inconsistent with ACI; correct value for c=14,d=9 is 92in.
- `beta = 1.0` (square). `alpha_s = 40` (interior per ACI 22.6.5.3).
- `vc1 = 4*lambda*sqrt(fc_psi)`
- `vc2 = (2 + 4/beta)*lambda*sqrt(fc_psi)`
- `vc3 = (2 + alpha_s*d_in/bo_in)*lambda*sqrt(fc_psi)`
- `vc_psi = min(vc1, vc2, vc3)`
- `Vc_lbf = vc_psi*bo_in*d_in`, `Vc_kip = Vc_lbf/1000`, `phiVn_kip = 0.75*Vc_kip`
- `Apunch_in2 = (c_in + d_in)^2`, `Apunch_ft2 = Apunch_in2/144`
- `Vu_lbf = qu_psf*(Af_ft2 - Apunch_ft2)`, `Vu_kip = Vu_lbf/1000`
- `ok_two_way = Vu_kip <= phiVn_kip`
- Also report `vc_psi`, `bo_in`.
### Check 4 — Flexure — ACI 22.5 / 7, phi=0.90
- Cantilever length `Lc_in = (Bf_in - c_in)/2`, `Lc_ft = Lc_in/12`
- `Mu_kipft = qu_psf * Bf_ft * Lc_ft^2 / 2` (qu as psf -> psf*ft*ft^2 = lbf*ft/1000 = kip*ft). Equivalent presentation: `Mu = qu*Bf*((Bf-c)/2)^2/2`.
- `a_in = As_in2*fy_psi / (0.85*fc_psi*Bf_in)`
- `c_block_in = a_in / beta1` where `beta1 = max(0.65, min(0.85, 0.85 - 0.05*max(0, (fc_psi-4000)/1000)))` (ACI 22.2.2.4.3). Computed for strain check but not required for phi (phi=0.9 tension-controlled assumed; still compute `et` for report).
- `beta1` per above.
- `Mn_kipft = As_in2*fy_ksi*(d_in - a_in/2)/12` (fy in ksi). Or `As*fy*(d-a/2)/12`.
- `phiMn_kipft = 0.90*Mn_kipft`
- `ok_flexure = Mu_kipft <= phiMn_kipft`
- `rho = As_in2 / (Bf_in*d_in)`, `rho_min = 0.0018`, `ok_min_steel = rho >= rho_min` (separate check `minimum_steel` with demand `rho_min`, capacity `rho`). For `checks` dict, `minimum_steel` uses `demand = rho_min`, `capacity = rho`.
- Also report `Mu/phiMn`, `a_in`, `rho`.
### Check 5 — Concrete bearing — ACI 22.8, phi=0.65
- `A1_in2 = bp_in^2`, `A2_in2 = Af_in2`
- `sqrt_ratio = sqrt(A2_in2/A1_in2)`, capped at 2.0: `sqrt_ratio_capped = min(sqrt_ratio, 2.0)`
- `Bn_lbf = 0.85*fc_psi*A1_in2*sqrt_ratio_capped`, but upper bound `2*0.85*fc_psi*A1_in2` already enforced by cap.
- `Bn_kip = Bn_lbf/1000`, `phiBn_kip = 0.65*Bn_kip`
- `ok_bearing = Pu_kip <= phiBn_kip`
- Report `A1_in2`, `A2_in2`, `sqrt_ratio`, `Bn_kip`, `phiBn_kip`, `Pu/phiBn`.
### Values dictionary (all rounded to 6 decimals via q() helper, except labels)
Keys in `results.json` `values` (units encoded in name):
`Ps_kip, Pu_kip, qa_psf, q_psf, qu_psf, Af_ft2,
Bf_in, Bf_ft, Df_in, cover_in, d_in,
fc_psi, fy_psi, fy_ksi, lambda,
c_in, bp_in,
N, rebar_size, db_in, As1_in2, As_in2,
rho, rho_min,
L1_in, Vu_one_way_kip, Vc_one_way_kip, phiVc_one_way_kip,
bo_in, vc_psi, Vu_two_way_kip, Vc_two_way_kip, phiVn_two_way_kip,
Lc_in, Mu_kipft, a_in, beta1, Mn_kipft, phiMn_kipft,
A1_in2, A2_in2, sqrt_ratio, Bn_kip, phiBn_kip`
### Checks dictionary
Each entry `{demand, capacity, ok}` with appropriate units (kip, kip-ft, psf, or dimensionless for rho):
- `soil_bearing`: demand `q_psf`, capacity `qa_psf`
- `one_way_shear`: demand `Vu_one_way_kip`, capacity `phiVc_one_way_kip`
- `two_way_shear`: demand `Vu_two_way_kip`, capacity `phiVn_two_way_kip`
- `flexure`: demand `Mu_kipft`, capacity `phiMn_kipft`
- `minimum_steel`: demand `rho_min`, capacity `rho`
- `bearing`: demand `Pu_kip`, capacity `phiBn_kip`
Overall ok requires all six true.
## Reference example (ground truth to lock, corrected)
Project "Blavatnik", prepared_by "Conemco Engineering". Derived loads shown in Typst: `DLr=10 psf`, `LLr=20 psf`, `Br=18.9 ft`, `Lr=27.5 ft`, `Ar=519.75 ft2`, column `14 in x14 in x14 ft`, `gamma_c=145 pcf`, `Ps~18.4 kip`, `Pu~26.2 kip`.
Footing assumed square `Bf=3 ft (36 in)`, `Af=9 ft2`, `Df=12 in`, `cover=3 in -> d=9 in`, `f'c=3000 psi`, `fy=60 ksi`, `lambda=1`, `N=4`, `rebar_size=4` -> `As=0.785 in2` (reference rounds to 0.8), `c=14 in`, `bp=6 in`, `qa=2500 psf`.
Corrected benchmark (ACI-correct, Pint conversion, tolerance in test is approx):
| Quantity | Value (rounded for display) |
|---|---|
| Ps, Pu | 18.4 kip, 26.2 kip (typst-derived 18.36/26.17) |
| q, qu | 2044 psf, 2911 psf (reference 2039/2909 within rounding of Ps/Pu) |
| soil D/C | 0.82 (q/qa) |
| One-way Vu | 1.46 kip |
| One-way Vc | 35.45 kip (2*sqrt(fc)*B*d) -> phiVc 26.59 kip, D/C 0.055 |
| Two-way bo | 92 in (corrected from 68) |
| vc | 219.1 psi (4*sqrt(fc)) |
| Two-way Vc | 181.4 kip -> phiVn 136.0 kip, Vu 15.51 kip, D/C 0.114 |
| Mu | 3.68 kip-ft |
| a | 0.524 in |
| Mn | 34.27 kip-ft -> phiMn 30.84 kip-ft, D/C 0.12 |
| rho | 0.00242 (>0.0018) |
| Bearing A1/A2 | 36 / 1296 in2, sqrt 6 capped 2 |
| Bn | 183.6 kip -> phiBn 119.3 kip, D/C 0.22 |
The reference PDF shows Vu one-way 1.5 kip, Vc 35.5 kip, phiVc 26.6 kip, Vu two-way 15.5 kip, Vc 134 kip (using 68in), phiVn 100.6 kip, Mu 3.7 kip-ft, Mn 34.3, phiMn 30.9. Differences are documented in `footing.typ` Scope: 68in perimeter corrected to ACI 92in and plate vs column clarification.
Pinned tolerances for pytest.approx: psf within 1%, kip within 0.02 kip, inches within 0.01, phi capacities within 0.3 kip or rel 1e-3.
## Conventions (inherited)
- `lib/sheet.typ` is shared. Do not modify; `footing.typ` uses `check` (not `check_service`) for all checks.
- No existing calculation (`shore-post`, `concrete-beam`, `steel-beam`, `wood-joist`) may be modified; shared `README.md` is allowed to add the new calc entry.
- Compile from `worksheets/` with `--root .`.
- Lock the reference example in pytest with `pytest.approx` before treating tool as stable.
## Milestones
- 001 DONE: `calc.py` + `input.yaml` + `results.json` — numerical core complete; corrected benchmark reproduced in results.json.
- 002 DONE: `test_concentric_footing.py` locks corrected benchmark (10 tests pass; reviewer PASS).
- 003 DONE: `footing.typ` + `generated/footing.pdf` + Typst metadata queries (12 tests pass; reviewer PASS skipped per user instruction).
- 004 DONE: `README.md` + `codemap.md` refreshed (depends on 003).
- 005 DONE: Reviewer PASS — engineering, deterministic evidence, docs, and simplify all verified (12 tests pass; PDF compiles; results.json idempotent).
## Final deliverables
- `calcs/concentric-footing/calc.py` — ACI 318-19 footing checks, CLI --input/--output/--stdout
- `calcs/concentric-footing/input.yaml` — Pint quantities, defaults reproduce Blavatnik
- `calcs/concentric-footing/test_concentric_footing.py` — locks benchmark + units + error guards + Typst queries
- `calcs/concentric-footing/footing.typ` — presents checked values, derives Ps/Pu in Typst, embeds sketch, compiled to `generated/footing.pdf`
- `README.md`, `codemap.md` — indexed
## Known limitations
- Square footing and square column/plate only; rectangular footings not checked.
- Interior column only (alpha_s=40); edge/corner punching not covered.
- Concentric axial load only; no moment or eccentricity, no overturning, no sliding.
- Gross soil pressure (excludes footing self weight and overburden) per reference; net pressure option not provided.
- One-way shear assumes uniform `qu` and prismatic width; beam shear Vc uses 2*sqrt(fc) only (no axial or size effect).
- Bearing uses `sqrt(A2/A1) <=2` per ACI 22.8.3.2; confinement reinforcement not checked.
- d = Df - cover (cover to centroid); bar diameter not subtracted separately. If cover is to clear, adjust input.
- Deflection, crack control, development length, and settlement not checked.
## Dependencies
- Python: `pyyaml`, `pytest`, `pint` (already in `requirements.txt`)
- `typst` CLI for compile and metadata-query tests
- No new third-party packages