calcs/concentric-footing/PROJECT_STATE.md
smillmorel d5ac3fca7e Add structural calculation worksheets
Collection of engineering calculation projects (Python + Typst), each with
input, calc script, tests, results, and generated PDF where available.
2026-09-21 12:19:20 -04:00

13 KiB

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 -> psfftft^2 = lbfft/1000 = kipft). 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)Bd) -> 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