calcs/concentric-footing/tasks/003_typst_sheet.md

163 lines
10 KiB
Markdown
Raw Normal View History

# 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.