Collection of engineering calculation projects (Python + Typst), each with input, calc script, tests, results, and generated PDF where available.
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:
- Soil bearing (service, ASD) —
q = Ps/Af <= qa - One-way (beam) shear — ACI 22.5 —
Vc = 2*lambda*sqrt(f'c)*Bf*d - Two-way (punching) shear — ACI 22.6 —
vc = min(4, 2+4/beta, 2+alpha_s*d/bo)*lambda*sqrt(f'c) - Flexure (bending) — ACI 22.5/7 — Whitney block
a = As*fy/(0.85*f'c*Bf),Mn = As*fy*(d-a/2) - 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()-> writesresults.json) ->footing.typ(presents only, no recomputation) -> compiled PDF with--root .fromworksheets/. results.jsonshape (unchanged contract):{tool, version, project, prepared_by, values, checks}withtool = "concentric_footing",version = "0.1".- Pint units: all dimensional inputs are quoted strings (e.g.
"3000 psi","3 ft","18.4 kip").calc.pyconverts with aquantity(value, unit, name)helper identical towood-joist/calc.py; dimensionless factors are plain floats. calc.pyCLI mirrorswood-joist/steel-beam:--input,--output,--stdout; runnable aspython calcs/concentric-footing/calc.pywith no args.- ACI 318-19 is the governing standard and edition. All clause references are to ACI 318-19 Chapter 22 / 13.
footing.typimports from../../lib/sheet.typand readsresults.json. It also derives loads in Typst (mirroringwood-joist/steel-beam):DLr,LLr,Br,Lr,Ar, column weightWc = 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-checkedPs/Puby the test suite. Capacity numbers are never recomputed in Typst.- Sheet helpers: flexure, shear, bearing, and soil bearing use
check(Demand/Capacity D/C). Nocheck_servicevariant is needed; soil bearing is presented asqvsqa. - Compilation:
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdfso the shared logo atassets/logo.pngresolves.
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, alsoBf_in = Bf_ft*12)Df-> in (total footing thickness)cover-> in (to centroid of steel, sod = Df - cover;dis effective depth)fc-> psi or ksi (concretef'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 toc)rebar_size-> int (e.g. 4 means #4 -> db = rebar_size/8 in)N-> int (number of bars per direction) Dimensionless / integers are validated:Ninteger >=1,rebar_sizeinteger 3..18,lambdain (0,1],rho_minhardcoded 0.0018.
Derived:
db_in = rebar_size/8As1_in2 = pi*db^2/4As_in2 = N*As1d_in = Df_in - cover_in(cover to centroid per reference;ValueErrorifd <=0ord > Df)Af_ft2 = Bf_ft^2,Af_in2 = Af_ft2*144A1_in2 = bp_in^2,A2_in2 = Af_in2,A2_ft2 = Af_ft2Bf_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_ft2wherePs_lbf = Ps_kip*1000qu_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
dfrom column face. L1_in = (Bf_in - c_in)/2 - d_in(cantilever beyond section). IfL1_in <=0thenVu_kip = 0(no shear beyond section).- Otherwise
Vu_lbf = qu_psf * (Bf_ft) * (L1_in/12)becausequ(psf) * width (ft) * length (ft). SoVu_kip = Vu_lbf/1000. Vc_lbf = 2*lambda*sqrt(fc_psi)*Bf_in*d_in(ACI 22.5.5.1,lambdafactor).Vc_kip = Vc_lbf/1000.phiVc_kip = 0.75*Vc_kipok_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_kipApunch_in2 = (c_in + d_in)^2,Apunch_ft2 = Apunch_in2/144Vu_lbf = qu_psf*(Af_ft2 - Apunch_ft2),Vu_kip = Vu_lbf/1000ok_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 / beta1wherebeta1 = 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 computeetfor report).beta1per above.Mn_kipft = As_in2*fy_ksi*(d_in - a_in/2)/12(fy in ksi). OrAs*fy*(d-a/2)/12.phiMn_kipft = 0.90*Mn_kipftok_flexure = Mu_kipft <= phiMn_kipftrho = As_in2 / (Bf_in*d_in),rho_min = 0.0018,ok_min_steel = rho >= rho_min(separate checkminimum_steelwith demandrho_min, capacityrho). Forchecksdict,minimum_steelusesdemand = 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_in2sqrt_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 bound2*0.85*fc_psi*A1_in2already enforced by cap.Bn_kip = Bn_lbf/1000,phiBn_kip = 0.65*Bn_kipok_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: demandq_psf, capacityqa_psfone_way_shear: demandVu_one_way_kip, capacityphiVc_one_way_kiptwo_way_shear: demandVu_two_way_kip, capacityphiVn_two_way_kipflexure: demandMu_kipft, capacityphiMn_kipftminimum_steel: demandrho_min, capacityrhobearing: demandPu_kip, capacityphiBn_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.typis shared. Do not modify;footing.typusescheck(notcheck_service) for all checks.- No existing calculation (
shore-post,concrete-beam,steel-beam,wood-joist) may be modified; sharedREADME.mdis allowed to add the new calc entry. - Compile from
worksheets/with--root .. - Lock the reference example in pytest with
pytest.approxbefore 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.pylocks 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.mdrefreshed (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/--stdoutcalcs/concentric-footing/input.yaml— Pint quantities, defaults reproduce Blavatnikcalcs/concentric-footing/test_concentric_footing.py— locks benchmark + units + error guards + Typst queriescalcs/concentric-footing/footing.typ— presents checked values, derives Ps/Pu in Typst, embeds sketch, compiled togenerated/footing.pdfREADME.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
quand prismatic width; beam shear Vc uses 2*sqrt(fc) only (no axial or size effect). - Bearing uses
sqrt(A2/A1) <=2per 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 inrequirements.txt) typstCLI for compile and metadata-query tests- No new third-party packages