Add structural calculation worksheets
Collection of engineering calculation projects (Python + Typst), each with input, calc script, tests, results, and generated PDF where available.
This commit is contained in:
commit
d5ac3fca7e
81 changed files with 18668 additions and 0 deletions
131
concentric-footing/tasks/001_calc_and_input.md
Normal file
131
concentric-footing/tasks/001_calc_and_input.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Task 001 — Numerical core: calc.py + input.yaml + results.json
|
||||
|
||||
## Goal
|
||||
|
||||
Implement the ACI 318-19 concentric footing calculator as the single source of truth for soil bearing, one-way shear, two-way shear, flexure, minimum steel, and concrete bearing.
|
||||
|
||||
## Background
|
||||
|
||||
Architecture is hybrid Pint pattern per PROJECT_STATE.md. This task establishes the contract every later task depends on: input.yaml Pint quantities, calc.py compute() with 6 checks, and results.json {tool,version,project,prepared_by,values,checks}. Parent conventions from wood-joist and steel-beam apply. No Typst work in this task.
|
||||
|
||||
The reference is CONCENTRIC-FOOTING.pdf (Blavatnik). Corrected benchmark is pinned in PROJECT_STATE.md (Bf=3ft, Af=9ft2, d=9in, fc=3000psi, fy=60ksi, Ps~18.4kip Pu~26.2kip, etc.). The 68in punching perimeter in the PDF is inconsistent; implement ACI-correct bo=4*(c+d)=92in.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `calcs/concentric-footing/calc.py` — create. Single module, no external deps beyond pyyaml/pint.
|
||||
- `calcs/concentric-footing/input.yaml` — create. Pint-quoted strings for all dimensional inputs.
|
||||
- `calcs/concentric-footing/results.json` — create (generated by running calc.py on the default input.yaml). Do not hand-edit.
|
||||
|
||||
If `calcs/concentric-footing/generated/` does not exist, do not create it here (Task 003 does).
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. input.yaml — defaults reproduce corrected Blavatnik example
|
||||
|
||||
Write YAML with these keys (order as listed, comments allowed):
|
||||
|
||||
```yaml
|
||||
project: "Blavatnik"
|
||||
prepared_by: "Conemco Engineering"
|
||||
|
||||
Ps: "18.4 kip" # service axial (checked demand)
|
||||
Pu: "26.2 kip" # factored axial (checked demand, 1.2D+1.6L)
|
||||
qa: "2500 psf" # allowable soil bearing (gross)
|
||||
Bf: "3 ft" # square footing side
|
||||
Df: "12 in" # total thickness
|
||||
cover: "3 in" # to centroid of steel -> d = Df - cover
|
||||
fc: "3000 psi" # f'c
|
||||
fy: "60 ksi" # fy
|
||||
lambda: 1 # lightweight factor (float, (0,1])
|
||||
column_width: "14 in" # c, square column
|
||||
base_plate_width: "6 in" # bp, square base plate (A1 = bp^2); if omitted in code defaults to c but YAML provides it
|
||||
N: 4 # bars per direction
|
||||
rebar_size: 4 # #4 -> db=0.5in
|
||||
|
||||
# Optional documentation keys (not used in compute, but keep for Typst derivation comments):
|
||||
# DLr, LLr etc are NOT in input.yaml; Typst derives Ps/Pu there. This YAML holds checked demands only.
|
||||
```
|
||||
|
||||
All quantities must be quoted strings so Pint parses them. `lambda`, `N`, `rebar_size` are unquoted numbers.
|
||||
|
||||
Accept alternative units via Pint (e.g. Bf as "36 in", fc as "3 ksi", Ps as "18400 lbf") — conversion handled in calc.py.
|
||||
|
||||
### 2. calc.py — implement compute(inp) -> dict
|
||||
|
||||
Create file at `calcs/concentric-footing/calc.py` with structure identical to `calcs/wood-joist/calc.py`:
|
||||
|
||||
- Imports: json, math, sys, argparse, pathlib Path, yaml, pint UnitRegistry, DimensionalityError etc.
|
||||
- Define `HERE = Path(__file__).resolve().parent`
|
||||
- `ureg = UnitRegistry()` and define `kip = 1000*force_pound`, `ksi = kip/inch**2`, `psf = force_pound/foot**2`, `pcf = force_pound/foot**3`, `plf`, `psi` if missing.
|
||||
- Helper `quantity(value, unit, name) -> float`: `ureg.Quantity(value).to(unit).magnitude` with ValueError on bad dimension or <=0. Message must include field name.
|
||||
- Helper `factor(value,name)` or inline validation for lambda (0<lambda<=1), integer checks for N and rebar_size.
|
||||
- `def compute(inp: dict) -> dict:` implements pinned equations from PROJECT_STATE.md **verbatim**:
|
||||
|
||||
1. Parse:
|
||||
```
|
||||
Ps_kip = quantity(inp["Ps"],"kip","Ps")
|
||||
Pu_kip = quantity(inp["Pu"],"kip","Pu")
|
||||
qa_psf = quantity(inp["qa"],"psf","qa")
|
||||
Bf_ft = quantity(inp["Bf"],"ft","Bf")
|
||||
Df_in = quantity(inp["Df"],"in","Df")
|
||||
cover_in = quantity(inp["cover"],"in","cover")
|
||||
fc_psi = quantity(inp["fc"],"psi","fc")
|
||||
fy_psi = quantity(inp["fy"],"psi","fy") # accept ksi via Pint -> psi
|
||||
fy_ksi = fy_psi/1000
|
||||
lambda_f = float(inp.get("lambda",1))
|
||||
c_in = quantity(inp["column_width"],"in","column_width")
|
||||
bp_in = quantity(inp.get("base_plate_width", inp["column_width"]),"in","base_plate_width")
|
||||
N = int(inp["N"]); rebar_size = int(inp["rebar_size"])
|
||||
```
|
||||
Validate lambda (0,1], N>=1, rebar_size 3..18, d_in = Df_in - cover_in >0 else ValueError, Bf_in = Bf_ft*12, Af_ft2 = Bf_ft**2, Af_in2 = Af_ft2*144.
|
||||
|
||||
2. Derived rebar: db_in = rebar_size/8.0, As1_in2 = pi*db^2/4, As_in2 = N*As1_in2, rho = As_in2/(Bf_in*d_in), rho_min=0.0018
|
||||
|
||||
3. Pressures: Ps_lbf=Ps_kip*1000, Pu_lbf=Pu_kip*1000, q_psf = Ps_lbf/Af_ft2, qu_psf = Pu_lbf/Af_ft2
|
||||
|
||||
4. One-way: L1_in = (Bf_in - c_in)/2 - d_in; Vu_one = 0 if L1_in<=0 else qu_psf*Bf_ft*(L1_in/12)/1000 (kip); Vc_one_lbf = 2*lambda_f* sqrt(fc_psi) * Bf_in * d_in; phiVc =0.75*Vc/1000
|
||||
|
||||
5. Two-way: bo=4*(c_in+d_in); beta=1; alpha=40; vc1=4*lambda*sqrt(fc), vc2=(2+4/beta)*lambda*sqrt(fc), vc3=(2+alpha*d/bo)*lambda*sqrt(fc); vc=min(...); Vc_two = vc*bo*d/1000; phiVn=0.75*Vc; Apunch = (c+d)^2 /144 ft2; Vu_two = qu*(Af - Apunch)/1000
|
||||
|
||||
6. Flexure: Lc_in=(Bf_in - c_in)/2; Lc_ft = Lc_in/12. Mu_kipft = qu_psf * Bf_ft * Lc_ft**2 / 2 / 1000 (qu psf * Bf ft * Lc_ft^2 / 2 = lbf-ft, /1000 = kip-ft). a_in = As_in2*fy_psi/(0.85*fc_psi*Bf_in); beta1 = max(0.65, min(0.85, 0.85 - 0.05*max(0, (fc_psi-4000)/1000))); Mn_kipft = As_in2*fy_ksi*(d_in - a_in/2)/12; phiMn_kipft = 0.9*Mn_kipft
|
||||
|
||||
7. Bearing: A1=bp^2, A2=Af_in2, sqrt_ratio = sqrt(A2/A1), capped 2.0, Bn=0.85*fc_psi*A1*capped/1000, phiBn=0.65*Bn
|
||||
|
||||
8. Build values dict with keys listed in PROJECT_STATE.md, each q(value,6) rounded. Include N, rebar_size, db, As1, As etc. Use helper q=lambda v: round(float(v),6).
|
||||
|
||||
9. Build checks dict with 6 entries: soil_bearing, one_way_shear, two_way_shear, flexure, minimum_steel, bearing each {demand, capacity, ok}. For minimum_steel demand=rho_min capacity=rho.
|
||||
|
||||
10. Return {tool:"concentric_footing", version:"0.1", project: inp.get("project",""), prepared_by: inp.get("prepared_by",""), values:..., checks:...}
|
||||
|
||||
- Implement `main(argv)` with argparse `--input/-i` default HERE/"input.yaml", `--output/-o` default beside input, `--stdout` flag. Mirrors wood-joist: read yaml, compute, write json indent2, print path or stdout.
|
||||
- `if __name__=="__main__": raise SystemExit(main())`
|
||||
|
||||
### 3. results.json
|
||||
|
||||
After writing calc.py and input.yaml, run `python calcs/concentric-footing/calc.py` (from worksheets root) to generate `calcs/concentric-footing/results.json`. Verify file exists and contains tool concentric_footing.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. `python calcs/concentric-footing/calc.py` exits 0 and writes `results.json` with `tool=="concentric_footing"` and `version=="0.1"`.
|
||||
2. Re-running with `--stdout` produces identical JSON to the file (idempotent).
|
||||
3. Converting alternative units yields same magnitudes: Bf "36 in" vs "3 ft", fc "3 ksi" vs "3000 psi", Ps "18400 lbf" vs "18.4 kip" within 1e-6 rel.
|
||||
4. Wrong dimension raises ValueError with field name (e.g. `Bf: "3 kip"`).
|
||||
5. Negative or zero values raise ValueError.
|
||||
6. `d = Df - cover` validation: if cover >= Df, ValueError.
|
||||
7. Values for default input within approx of corrected benchmark: q ~2044 psf (±30), qu~2911 psf, one-way phiVc~26.6 kip, two-way phiVn~136 kip, Mu~3.68 kip-ft, phiMn~30.8 kip-ft, rho~0.0024, phiBn~119 kip. (Exact locks in Task 002.)
|
||||
8. No existing calcs are modified.
|
||||
|
||||
## Tests
|
||||
|
||||
Do not create tests in this task. Manual verification commands (builder runs, reviewer will run pytest after Task 002):
|
||||
|
||||
```
|
||||
python -m pip install -r requirements.txt
|
||||
python calcs/concentric-footing/calc.py
|
||||
python calcs/concentric-footing/calc.py --stdout | python -m json.tool
|
||||
python -c "import yaml, importlib.util; spec=importlib.util.spec_from_file_location('c','calcs/concentric-footing/calc.py'); m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m); print(m.compute(yaml.safe_load(open('calcs/concentric-footing/input.yaml'))))"
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
110
concentric-footing/tasks/002_numerical_tests.md
Normal file
110
concentric-footing/tasks/002_numerical_tests.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# 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.
|
||||
163
concentric-footing/tasks/003_typst_sheet.md
Normal file
163
concentric-footing/tasks/003_typst_sheet.md
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# Task 003 — Presentation: footing.typ + compile + Typst metadata tests
|
||||
|
||||
## Goal
|
||||
|
||||
Present the checked numbers in a printable Typst sheet that also derives the Blavatnik gravity loads inline, emits queryable metadata, and compiles to PDF.
|
||||
|
||||
## Background
|
||||
|
||||
Architecture: `footing.typ` imports `../../lib/sheet.typ` and `json("results.json")`. It must not recompute capacities; it only presents `results.json` values via `calc-line` and `check` boxes. Load determination (roof DL/LL, tributary area, column self weight) is derived in Typst and reconciled to Python's checked `Ps`/`Pu` by the test suite — mirroring `wood-joist` and `steel-beam` where `<wood-joist-loads>` is queried.
|
||||
|
||||
The `check`, `calc-line`, and `calculation-sheet` helpers are defined in `lib/sheet.typ` (worksheets root). Signatures used here: `check(label, demand, capacity, unit:, ok:)`, `calc-line(content, note)`, and `calculation-sheet.with(title:, project:, prepared-by:)`.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `calcs/concentric-footing/footing.typ` — create.
|
||||
- `calcs/concentric-footing/generated/footing.pdf` — create via `typst compile --root .`.
|
||||
- `calcs/concentric-footing/test_concentric_footing.py` — append two Typst query tests (do not rewrite existing tests); create `generated/` dir if needed.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. footing.typ structure
|
||||
|
||||
Follow `calcs/wood-joist/beam.typ` as template:
|
||||
|
||||
- Header:
|
||||
```
|
||||
#import "../../lib/sheet.typ": calc-line, calculation-sheet, check
|
||||
#let data = json("results.json")
|
||||
#let n = data.values
|
||||
#let checks = data.checks
|
||||
#let round(value, digits: 2) = calc.round(value, digits: digits)
|
||||
#show: calculation-sheet.with(title: "Concentric Footing Analysis", project: data.project, prepared-by: data.prepared_by)
|
||||
= Concentric Footing Analysis
|
||||
Square spread footing under concentric axial load, ACI 318-19. Numbers come from `calc.py`; this sheet only presents them. Typst derives gravity loads below; checked Python demands are reconciled by the test suite.
|
||||
```
|
||||
|
||||
- Add a simple footing sketch using Typst `box`/`rect`/`line` drawing (no external image needed). Example: centered square footing with column on top, label `Bf` and `Df`. Wrap in `#figure(..., caption: [Footing plan and section: #n.Bf_ft ft × #n.Bf_ft ft × #n.Df_in in, d=#n.d_in in.])`. Keep geometry text as `calc-line` below.
|
||||
|
||||
- Section `== Loads Determination`:
|
||||
Derive Blavatnik loads in Typst (visible arithmetic):
|
||||
```
|
||||
#let DLr = 10 // psf
|
||||
#let LLr = 20 // psf
|
||||
#let Br = 18.9 // ft
|
||||
#let Lr = 27.5 // ft
|
||||
#let Ar = Br * Lr // ft2
|
||||
#let bc = 14 // in
|
||||
#let Lc = 14 // ft
|
||||
#let gamma_c = 145 // pcf
|
||||
#let Wc_kip = bc * bc / 144 * Lc * gamma_c / 1000 // kip column weight
|
||||
#let Ps_typst = (DLr + LLr) * Ar / 1000 + Wc_kip // kip
|
||||
#let Pu_typst = 1.2*(DLr*Ar/1000 + Wc_kip) + 1.6*LLr*Ar/1000
|
||||
#metadata((Ps_kip: Ps_typst, Pu_kip: Pu_typst, Ar_ft2: Ar, Wc_kip: Wc_kip)) <concentric-footing-loads>
|
||||
```
|
||||
Then emit `calc-line` rows:
|
||||
- `Ar = Br·Lr = #round(Ar,2) ft2` (tributary area)
|
||||
- `Wc = bc·bc·Lc·γc = #round(Wc_kip,2) kip` (column self weight)
|
||||
- `Ps = (DLr+LLr)·Ar + Wc = #round(Ps_typst,2) kip (Typst-derived)`
|
||||
- `Ps_checked = #round(n.Ps_kip,2) kip (Python-checked)`
|
||||
- `Pu = 1.2(DLr·Ar+Wc)+1.6·LLr·Ar = #round(Pu_typst,2) kip (Typst-derived)`
|
||||
- `Pu_checked = #round(n.Pu_kip,2) kip`
|
||||
- Also show `qu = Pu/Af = #round(n.qu_psf,1) psf` for context but do not recompute Af.
|
||||
|
||||
- Section `== Geometry and Materials`:
|
||||
`calc-line` for Bf, Df, cover, d, Af, c, bp, fc, fy, lambda, N, rebar_size, As, rho. Example:
|
||||
`calc-line([$B_f = #n.Bf_ft " ft"$, $A_f = #n.Af_ft2 " ft"^2$], [Footing plan area])` etc. Show As1, As, rho, rho_min.
|
||||
|
||||
- Section `== Soil Bearing`:
|
||||
`calc-line([$q = P_s/A_f = #round(n.q_psf,1) " psf"$], [Acting service pressure])`
|
||||
`calc-line([$q_a = #n.qa_psf " psf"$], [Allowable])`
|
||||
Then `#check("Soil bearing", checks.soil_bearing.demand, checks.soil_bearing.capacity, unit: "psf", ok: checks.soil_bearing.ok)`
|
||||
Also show `qu` as info line.
|
||||
|
||||
- Section `== One-Way Shear`:
|
||||
`calc-line([$L_1 = (B_f - c)/2 - d = #round(n.L1_in,2) " in"$], [Cantilever beyond d])`
|
||||
`calc-line([$V_u = q_u B_f L_1 = #round(n.Vu_one_way_kip,2) " kip"$], [Demand at d])`
|
||||
`calc-line([$V_c = 2 lambda sqrt(f'_c) B_f d = #round(n.Vc_one_way_kip,1) " kip"$], [ACI 22.5])`
|
||||
`calc-line([$phi V_c = #round(n.phiVc_one_way_kip,1) " kip"$], [phi=0.75])`
|
||||
`#check("One-way shear", checks.one_way_shear.demand, checks.one_way_shear.capacity, unit: "kip", ok: checks.one_way_shear.ok)`
|
||||
|
||||
- Section `== Two-Way Shear (Punching)`:
|
||||
`calc-line([$b_o = 4(c+d)= #n.bo_in " in"$], [Critical perimeter, d/2])`
|
||||
`calc-line([$v_c = min(4,2+4/beta,2+alpha d/b_o) lambda sqrt(f'_c)= #round(n.vc_psi,1) " psi"$], [ACI 22.6])`
|
||||
`calc-line([$V_c = v_c b_o d = #round(n.Vc_two_way_kip,1) " kip"$], [])`
|
||||
`calc-line([$V_u = q_u(A_f - (c+d)^2)= #round(n.Vu_two_way_kip,1) " kip"$], [Punch demand])`
|
||||
`#check("Two-way shear", checks.two_way_shear.demand, checks.two_way_shear.capacity, unit: "kip", ok: checks.two_way_shear.ok)`
|
||||
|
||||
- Section `== Flexure`:
|
||||
`calc-line([$L_c = (B_f - c)/2 = #round(n.Lc_in,1) " in"$], [Cantilever])`
|
||||
`calc-line([$M_u = q_u B_f L_c^2/2 = #round(n.Mu_kipft,2) " kip·ft"$], [])`
|
||||
`calc-line([$a = A_s f_y/(0.85 f'_c B_f)= #round(n.a_in,3) " in"$], [Whitney block])`
|
||||
`calc-line([$M_n = A_s f_y(d -a/2)= #round(n.Mn_kipft,1) " kip·ft"$], [])`
|
||||
`calc-line([$rho = A_s/(B_f d)= #round(n.rho,4)$], [vs rho_min 0.0018])`
|
||||
`#check("Flexure", checks.flexure.demand, checks.flexure.capacity, unit: "kip·ft", ok: checks.flexure.ok)`
|
||||
`#check("Minimum steel", checks.minimum_steel.demand, checks.minimum_steel.capacity, unit: "", ok: checks.minimum_steel.ok)` — for rho, show ratio but check helper expects demand/capacity; use demand=rho_min capacity=rho; label accordingly.
|
||||
|
||||
- Section `== Concrete Bearing`:
|
||||
`calc-line([$A_1 = b_p^2 = #round(n.A1_in2,1) " in"^2$, $A_2 = B_f^2 = #round(n.A2_in2,1) " in"^2$], [Plate and footing])`
|
||||
`calc-line([$sqrt(A_2/A_1)= #round(n.sqrt_ratio,2)$, capped at 2], [])`
|
||||
`calc-line([$B_n = 0.85 f'_c A_1 sqrt(...)= #round(n.Bn_kip,1) " kip"$], [ACI 22.8])`
|
||||
`calc-line([$phi B_n = #round(n.phiBn_kip,1) " kip"$], [phi=0.65])`
|
||||
`#check("Concrete bearing", checks.bearing.demand, checks.bearing.capacity, unit: "kip", ok: checks.bearing.ok)`
|
||||
|
||||
- Section `== Scope And Limitations`:
|
||||
Text stating square footing only, interior concentric axial only, gross pressure, no moment/overturning/sliding, no settlement, d from cover to centroid, bo correction noted (reference 68in corrected to ACI 92in), plate vs column clarification, no deflection/crack/development, final design by engineer.
|
||||
|
||||
- Footer metadata for reconciliation:
|
||||
```
|
||||
#metadata((soil_util: checks.soil_bearing.demand/checks.soil_bearing.capacity, one_way_util: checks.one_way_shear.demand/checks.one_way_shear.capacity, two_way_util: checks.two_way_shear.demand/checks.two_way_shear.capacity, flexure_util: checks.flexure.demand/checks.flexure.capacity, bearing_util: checks.bearing.demand/checks.bearing.capacity, q_psf: n.q_psf, qu_psf: n.qu_psf)) <concentric-footing-results>
|
||||
```
|
||||
|
||||
Ensure all `n.*` keys match Task 001 values dict exactly.
|
||||
|
||||
### 2. Compile
|
||||
|
||||
From `worksheets/` run:
|
||||
```
|
||||
python calcs/concentric-footing/calc.py
|
||||
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||
```
|
||||
Verify PDF exists and >50KB.
|
||||
|
||||
### 3. Append Typst query tests
|
||||
|
||||
Append to `calcs/concentric-footing/test_concentric_footing.py` two new tests (do not delete existing 10):
|
||||
|
||||
- `test_typst_compiles_and_presents_python_numbers(result)` — runs `calc.py` with `subprocess.run([sys.executable, str(HERE/"calc.py")], check=True, cwd=HERE.parents[1])`, then `typst compile ...` check pdf exists, then `typst query` for `<concentric-footing-results>` via `typst eval "query(<concentric-footing-results>)"`? Actually use `typst eval` or `typst query` command as in wood-joist: `subprocess.run(["typst","query", ...])`? Wood-joist uses `typst compile` and `typst eval query`. Use same pattern as wood-joist test:
|
||||
```
|
||||
subprocess.run(["typst","compile","--root",".",...], check=True, cwd=HERE.parents[1])
|
||||
query = subprocess.run(["typst","query","..."]?)
|
||||
```
|
||||
Check wood-joist: it runs `["typst","eval","query(<wood-joist-results>)","--root",...,"--in","calcs/wood-joist/beam.typ","--format","json"]`. Mirror that: `["typst","query", str(HERE/"footing.typ"), "<concentric-footing-results>", "--root", "."]` may vary; adapt to whatever works but assert exactly one entry and that flexure_util approx equals Python f. Specifically query the published metadata and assert `meta["flexure_util"] approx values["Mu"]/values["phiMn"]` etc for soil, one-way, two-way, bearing.
|
||||
|
||||
- `test_typst_load_demands_match_checked_inputs(result)` — queries `<concentric-footing-loads>` and asserts `derived Ps_kip approx values["Ps_kip"]` within 0.1 kip (allow rounding of column weight 18.36 vs 18.4) and same for `Pu_kip`.
|
||||
|
||||
Implementation must be robust: if `typst query` syntax differs, use `typst compile` + `typst eval` as in wood-joist. Ensure tests pass on the CI image where typst is installed.
|
||||
|
||||
If `generated/` does not exist, create it with `Path(...).mkdir(parents=True, exist_ok=True)`.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. `footing.typ` imports only `lib/sheet.typ` and `results.json`; no arithmetic beyond load derivation and rounding.
|
||||
2. Every `check` box reflects `checks.*.ok` from JSON; D/C printed as demand/capacity.
|
||||
3. `typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf` succeeds and PDF exists.
|
||||
4. Two appended pytest tests pass, reconciling Typst-derived Ps/Pu to Python-checked Ps/Pu and Typst-presented utils to Python values.
|
||||
5. All prior 10 tests still pass (total 12).
|
||||
6. No existing calc files modified; `generated/` is git-ignored regenerable.
|
||||
|
||||
## Tests
|
||||
|
||||
Builder runs:
|
||||
|
||||
```
|
||||
python calcs/concentric-footing/calc.py
|
||||
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||
```
|
||||
|
||||
Expected: 12 passed, PDF exists.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Tasks 001, 002 must be DONE.
|
||||
73
concentric-footing/tasks/004_docs_refresh.md
Normal file
73
concentric-footing/tasks/004_docs_refresh.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Task 004 — Documentation refresh: README.md + codemap.md
|
||||
|
||||
## Goal
|
||||
|
||||
Index the new concentric footing calculation in the shared documentation so the next engineer can discover and compile it without reading source.
|
||||
|
||||
## Background
|
||||
|
||||
Depends on footing.typ existing. This task is typst-builder-local (documentation only). Conventions per PROJECT_STATE.md: compile with --root ., hybrid pattern, no modification to existing calcs.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `README.md` — the shared worksheets readme that lists calcs. At present this file lives at `calcs/wood-joist/README.md` which serves as the de-facto worksheets README (it documents all sheets). Also check for `README.md` at `worksheets/` root if it exists. Update whichever exists; if both exist update both with same concentric-footing entry. If only `calcs/wood-joist/README.md` exists, that is the file to edit.
|
||||
- `codemap.md` — update at `worksheets/codemap.md` (and copy to `calcs/concentric-footing/codemap.md` if the per-calc map is expected). Regenerate the whole map rather than editing in place per codemap rules.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. README.md
|
||||
|
||||
Add a new section `## Concentric footing` after the Wood joist section, following the same style. Include:
|
||||
|
||||
- One-sentence description: "Square concentric spread footing under axial load per ACI 318-19 — soil bearing, one-way/two-way shear, flexure, minimum steel, concrete bearing."
|
||||
- Compile commands:
|
||||
```
|
||||
python calcs/concentric-footing/calc.py
|
||||
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||
python -m pytest calcs/concentric-footing/test_concentric_footing.py
|
||||
```
|
||||
- Stdout variant:
|
||||
```
|
||||
python calcs/concentric-footing/calc.py --input calcs/concentric-footing/input.yaml --stdout
|
||||
```
|
||||
and `--output` note.
|
||||
- Note on load derivation: `input.yaml` supplies checked `Ps`/`Pu` (Pint quantities) and footing geometry/materials; `footing.typ` derives `Ps`/`Pu` inline from `DLr=10psf, LLr=20psf, Br=18.9ft, Lr=27.5ft, column 14in×14ft @145pcf` and reconciles via `<concentric-footing-loads>` query; bearing plate width `bp=6in` governs `A1`.
|
||||
- Update the top index code block to add line:
|
||||
```
|
||||
calcs/concentric-footing/ YAML quantities → Pint → JSON → Typst (ACI 318-19)
|
||||
```
|
||||
- Ensure the 5-step New calculation list remains unchanged.
|
||||
|
||||
If editing `worksheets/README.md` vs `calcs/wood-joist/README.md`, keep both in sync; prefer editing the file that actually exists and then copying the change to the other if both are present.
|
||||
|
||||
### 2. codemap.md
|
||||
|
||||
Regenerate at `worksheets/codemap.md` (and also write copy to `calcs/concentric-footing/codemap.md` if needed for per-calc root check):
|
||||
|
||||
- Re-run classification for all 37+ files including the new `calcs/concentric-footing/calc.py [logic]`, `input.yaml [config]`, `footing.typ [logic]`, `test_concentric_footing.py [test]`, `results.json [state]`, `generated/footing.pdf [doc]`, `CONCENTRIC-FOOTING.pdf [doc]`.
|
||||
- The layout section must now show `calcs/concentric-footing/` expanded with its 5 planned + 3 reference entries.
|
||||
- Hot Spots must now include `calcs/concentric-footing/calc.py` as single source of truth.
|
||||
- Conventions section must mention the new sheet's hybrid Pint pattern and Typst load derivation.
|
||||
- Timestamp updated to now, Files indexed incremented.
|
||||
|
||||
Do not write summaries from imagination; if uncertain read first 20 lines.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. `README.md` contains a new `## Concentric footing` section with compile, stdout, and load-derivation notes; top index code block lists the new calc.
|
||||
2. `codemap.md` at `worksheets/codemap.md` exists, is well-formed markdown with header, layout, hot spots, conventions; it lists `calcs/concentric-footing/` and its files.
|
||||
3. `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v` still passes (12 tests).
|
||||
4. No existing calc logic files modified; only docs/maps changed.
|
||||
|
||||
## Tests
|
||||
|
||||
```
|
||||
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||
```
|
||||
|
||||
Both succeed; PDF still compiles.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 003 must be DONE.
|
||||
69
concentric-footing/tasks/005_review.md
Normal file
69
concentric-footing/tasks/005_review.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Task 005 — Reviewer pass on the whole calculation
|
||||
|
||||
## Goal
|
||||
|
||||
Verify engineering correctness, deterministic evidence, and documentation for the concentric footing analysis before marking the project done.
|
||||
|
||||
## Background
|
||||
|
||||
This task is reviewer-local (reasoning) with simplify skill pass. It must not modify source except via review report; if issues are found the orchestrator will send them back to the builder. The reviewer must check ACI 318-19 clause references, units, applicability limits, benchmark tolerances, and that the Typst sheet presents only (no recomputation) per PROJECT_STATE.md.
|
||||
|
||||
## Files to Modify
|
||||
|
||||
No files to modify in this task. The reviewer writes a review note (verbal output) and runs the simplify skill as part of the standard review pass. If STATUS: FAIL, the orchestrator updates TASKS.md and re-delegates.
|
||||
|
||||
If the reviewer finds fixable issues, they must be reported as a structured list with file and line references.
|
||||
|
||||
## Implementation
|
||||
|
||||
Reviewer steps (read-only, then run commands via delegation tool if needed — but orchestrator handles command execution; reviewer describes what to run and checks output):
|
||||
|
||||
1. Read `PROJECT_STATE.md`, `TASKS.md`, `calcs/concentric-footing/calc.py`, `input.yaml`, `results.json`, `footing.typ`, `test_concentric_footing.py`, `README.md`, `codemap.md`, and `CONCENTRIC-FOOTING.pdf`.
|
||||
2. Verify engineering spec. Formula → code-section map (all in `calc.py:compute()`):
|
||||
- Soil bearing `q=Ps/Af`, `qu=Pu/Af`: Pressures block → `q_psf`, `qu_psf`.
|
||||
- One-way shear `Vc=2·λ·√fc·B·d`, `Vu=qu·B·L1`: One-way shear block → `L1_in`, `Vu_one_way_kip`, `Vc_one_way_kip`, `phiVc_one_way_kip`.
|
||||
- Two-way shear `bo=4(c+d)`, `vc=min(4,2+4/β,2+α·d/bo)·λ·√fc`, `Vu=qu·(Af-(c+d)²)`: Two-way shear block → `bo_in`, `vc_psi`, `Vu_two_way_kip`, `Vc_two_way_kip`, `phiVn_two_way_kip`.
|
||||
- Flexure `Mu=qu·B·Lc²/2`, `a=As·fy/(0.85·fc·B)`, `Mn=As·fy·(d-a/2)`: Flexure block → `Lc_in`, `Mu_kipft`, `a_in`, `beta1`, `Mn_kipft`, `phiMn_kipft`.
|
||||
- Bearing `Bn=0.85·fc·A1·min(√(A2/A1),2)`: Concrete bearing block → `A1_in2`, `A2_in2`, `sqrt_ratio`, `Bn_kip`, `phiBn_kip`.
|
||||
- Soil bearing `q=Ps/Af` and `qu=Pu/Af` correctly use Af=Bf^2 in ft2, Ps/Pu in kip, qa in psf, D/C correct.
|
||||
- One-way shear Vc=2*lambda*sqrt(fc)*B*d with phi 0.75, L1 = (B-c)/2 - d, Vu = qu*B*L1, correct unit conversion lbf->kip, handles L1<=0 edge.
|
||||
- Two-way shear bo=4*(c+d), vc=min(4,2+4/beta,2+alpha*d/bo)*lambda*sqrt(fc) with alpha 40 interior, beta 1, phi 0.75, Vu=qu*(Af - (c+d)^2), bo correction documented.
|
||||
- Flexure Mu=qu*B*Lc^2/2, a=As*fy/(0.85*fc*B), Mn=As*fy*(d-a/2), phi 0.9, beta1 formula per ACI, rho vs 0.0018.
|
||||
- Bearing Bn=0.85*fc*A1*min(sqrt(A2/A1),2), phi 0.65, A1=bp^2, A2=Bf^2, sqrt cap.
|
||||
- d = Df - cover to centroid, validation, lambda bounds, rebar area from #size.
|
||||
3. Verify deterministic evidence:
|
||||
- Run `python -m pytest calcs/concentric-footing/test_concentric_footing.py -v` and confirm 12 passed, no skipped.
|
||||
- Run `python calcs/concentric-footing/calc.py --stdout | python -m json.tool` and confirm idempotent vs results.json (diff empty).
|
||||
- Run `typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf` and confirm PDF exists and size >50KB.
|
||||
- Run Typst queries for `<concentric-footing-loads>` and `<concentric-footing-results>` and confirm reconciliation within tolerances.
|
||||
4. Check scope/limitations note in footing.typ mentions square only, interior only, gross pressure, no moment, bo correction, plate clarification, d definition, and that existing calc files were not touched.
|
||||
5. Run simplify skill pass on the diff (calc.py + footing.typ + test file) for readability, no behavior change.
|
||||
6. Return STATUS: PASS if all 8 checks below are satisfied, else STATUS: FAIL with enumerated issues.
|
||||
|
||||
## Acceptance Criteria (all must be true for PASS)
|
||||
|
||||
1. All six checks pass for default input; D/C values within expected ranges (soil ~0.82, one-way ~0.05, two-way ~0.11, flexure ~0.12, bearing ~0.22).
|
||||
2. Corrected punching perimeter bo=92in is used; review notes the PDF's 68in deviation and that the sheet's Scope documents it.
|
||||
3. Bearing plate vs column clarified (A1=36in2, A2=1296in2, Bn 183.6kip); alternative use of column size would be noted.
|
||||
4. Pint unit conversions tested (alternate units test passes) and wrong-dimension guards raise ValueError with field name.
|
||||
5. Effective depth d = Df - cover correctly validated; zero/negative d raises ValueError.
|
||||
6. Typst sheet presents only (no capacity recomputation), derives loads inline, emits both `<concentric-footing-loads>` and `<concentric-footing-results>` metadata, and compiles.
|
||||
7. README and codemap refreshed and list the new calculation; no existing calc files were modified (git diff or file timestamps check).
|
||||
8. Pytest 12 passed, PDF compiles, results.json idempotent, simplify pass clean (no behavior change).
|
||||
|
||||
## Tests
|
||||
|
||||
Reviewer (via orchestrator delegation) runs:
|
||||
|
||||
```
|
||||
python -m pip install -r requirements.txt
|
||||
python -m pytest calcs/concentric-footing/test_concentric_footing.py -v
|
||||
python calcs/concentric-footing/calc.py --stdout
|
||||
typst compile --root . calcs/concentric-footing/footing.typ calcs/concentric-footing/generated/footing.pdf
|
||||
```
|
||||
|
||||
All succeed; 12 passed.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 004 must be DONE.
|
||||
Loading…
Reference in a new issue