163 lines
10 KiB
Markdown
163 lines
10 KiB
Markdown
|
|
# 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.
|