# Project State: Wood Joist Analysis Last updated: 2026-08-20 ## Overview New hybrid (Typst + Python) calculation at `calcs/wood-joist/` that analyzes an existing wood joist under uniform gravity loads per **NDS 2018 Allowable Stress Design (ASD)**. Four checks are covered: 1. Flexure (bending) — NDS 3.3 (beam stability factor `C_L`) 2. Shear — NDS 3.4 3. Bearing (compression perpendicular to grain) — NDS 3.10 4. Serviceability (deflection) — NDS 3.5, limits L/240 (total) and L/360 (live) The default `input.yaml` reproduces a reference worked example ("Analysis for existing wood joists under shoring loads", Conemco Engineering, project "Pompano Beach") exactly. The pytest suite locks those numbers. ## Architecture decisions - **Hybrid pattern** (same as `steel-beam`): `input.yaml` (Pint unit-bearing quantities) → `calc.py` (computes, writes `results.json`) → `beam.typ` (presents only, no recomputation) → compiled PDF. - **`results.json` shape** (unchanged contract): `{tool, version, project, prepared_by, values, checks}`. `tool = "wood_joist"`, `version = "0.1"`. - **Pint units**: quantities are quoted strings in YAML (e.g. `"25 psf"`, `"9 ft"`). `calc.py` converts with a `quantity()` helper identical in spirit to `steel-beam/calc.py`. Dimensionless adjustment factors are plain floats. - **`calc.py` CLI** mirrors `steel-beam`: `--input`, `--output`, `--stdout` flags; runnable as `python calcs/wood-joist/calc.py` with no args. - **NDS 2018** is the governing standard and edition. All clause references are to NDS 2018. - **`beam.typ` imports** from `../../lib/sheet.typ` and reads `results.json` (no recomputation). Compiled with `--root .` from the worksheets root so the shared logo resolves. ## 2026-08-20 change: load determination moved to Typst (user-approved) This change moves wood-joist gravity-load determination from Python to Typst, mirroring the `steel-beam` pattern. No engineering equations changed — only where load determination lives. The Pompano Beach benchmark values are unchanged. - **`beam.typ` determines loads inline.** It binds `DL = 25 psf`, `LL = 50 psf`, span `L = 9 ft`, spacing `B = 13 ft` as Typst `#let` values, then derives `w = (DL + LL)·B`, `wL = LL·B`, `M = w·L²/8`, `V = w·L/2`, `R = V`. These are emitted as a Typst metadata label `` with fields `w_plf`, `wL_plf`, `M_ftlbf`, `V_lbf`, `R_lbf`. `beam.typ` presents both the Typst load-derived values and the Python-checked values (like steel-beam's `M_u,load` vs `M_u,design`), and the test suite reconciles them via the metadata query. - **New `input.yaml` contract.** `DL`, `LL`, and `spacing` are removed. The checked demands are supplied as Pint-quantity strings: `w: "975 plf"`, `wL: "650 plf"`, `M: "9871.875 ft * lbf"`, `V: "4387.5 lbf"`, `R: "4387.5 lbf"`. `span` is kept (Python needs it for the L/240 and L/360 deflection limits). All other keys are unchanged (`width`, `depth`, `bearing_length`, `bearing_end_distance`, `unbraced_length`, `species_grade`, NDS reference values, adjustment factors, `project`, `prepared_by`). - **`calc.py` reads demands.** It no longer performs load-path arithmetic. `w_plf`, `wL_plf`, `M_ftlbf`, `V_lbf`, `R_lbf` come from `quantity()` on the YAML demand keys. Section properties, all NDS 2018 ASD checks, and the `{tool, version, project, prepared_by, values, checks}` contract are unchanged (`tool = "wood_joist"`). `values` no longer includes `DL_psf`, `LL_psf`, or `spacing_ft`. - **`check_service` added to `lib/sheet.typ`** as a sibling of `check`. Signature `check_service(label, acting, allowed, unit: "", ok: auto)`. Identical layout (label, OK/NOT OK badge, colored box, ratio) but the left column reads **Acting** / **Allowed** (instead of Demand / Capacity) and the ratio is labeled **Utilization** (instead of D/C). Used only by the two deflection checks in `calcs/wood-joist/beam.typ`; flexure, shear, and bearing keep `check`. - **Convention change.** The inherited "`lib/sheet.typ` must not be modified" convention is superseded by this explicit user request. The change is **additive only**: `check` is unchanged and no other sheet (`steel-beam`, `concrete-beam`, `concrete-beam2`, `shore-post`) is modified. Pinned benchmark (unchanged): `w` 975 plf, `wL` 650 plf, `M` 9871.88 ft·lbf (exact 9871.875), `V` 4387.5 lbf, `R` 4387.5 lbf; flexure util 0.938, shear util 0.66, bearing util 0.772, Δ_total 0.339 in, Δ_live 0.226 in. ## Engineering decisions (pinned for the builder) The equations are fully pinned by the reference worked example (every intermediate value and formula is supplied). **No separate engineering specifier pass was required**; the equations reproduce NDS 2018 directly and the clause references are recorded here and in `tasks/001`. The reviewer task (`tasks/005`) still verifies the engineering specification. ### Section properties (rectangular joist) - `A = b·d` - `I_x = b·d³ / 12` - `S_x = b·d² / 6` (= `2·I_x / d`) ### Check 1 — Flexure (NDS 3.3) - Slenderness: `R_B = sqrt(l_e · d / b²)` (NDS 3.3.3.7). Require `R_B < 50`. If `R_B >= 50`, `calc.py` raises `ValueError` (member needs lateral bracing; the `C_L` equation is out of scope). - `F*_b = F_b · C_D · C_M · C_t · C_F · C_i · C_r` (all factors except `C_L`, `C_fu`). - `F_bE = 1.20 · E'_min / R_B²` - `C_L = (1 + F_bE/F*_b)/1.9 − sqrt(((1 + F_bE/F*_b)/1.9)² − (F_bE/F*_b)/0.95)` (NDS 3.3.3, `c_b = 0.95` for sawn lumber). Formula yields `C_L ≤ 1.0`. - `F'_b = F*_b · C_L · C_fu` - `f_b = M / S_x` (M in lbf·in) - Allowable moment `M_a = S_x · F'_b` (reported in ft·lbf). ### Check 2 — Shear (NDS 3.4) - `F'_v = F_v · C_D · C_M · C_t · C_vr` - `f_v = 3·V / (2·A)` (rectangular section, NDS 3.4.1 Eq. 3.4-1) ### Check 3 — Bearing (NDS 3.10, compression perpendicular to grain) - Bearing area factor `C_b = (l_b + 0.375) / l_b` (NDS 3.10.4; bearing < 6 in and ≥ 3 in from member end). - `F'_c⊥ = F_c⊥ · C_M · C_t · C_i · C_b` - Bearing area `A_b = l_b · b` - `f_c⊥ = R / A_b` (`R = w·L/2`) ### Check 4 — Serviceability (NDS 3.5) - `Δ_total = 5·w·L⁴ / (384·E·I_x)` - `Δ_live = 5·w_L·L⁴ / (384·E·I_x)` - Limits `L/240` (total) and `L/360` (live). **Hardcoded constants** in `calc.py` (`240`, `360`) — not YAML inputs — matching the reference. ## Reference example (ground truth to lock) Project "Pompano Beach", prepared_by "Conemco Engineering". | Quantity | Value | |---|---| | DL, LL | 25 psf, 50 psf | | span L, spacing B | 9 ft, 13 ft | | w, w_L | 975 plf, 650 plf | | b × d | 3.5 in × 9.5 in | | A, I_x, S_x | 33.25 in², 250.1 in⁴, 52.6 in³ | | F_b, F_v, F_c⊥, E, E'_min | 2400, 300, 565, 1,700,000, 510,000 psi | | All adjustment factors | 1.0 | | M, V, R | 9871.88 ft·lbf, 4387.5 lbf, 4387.5 lbf | | Flexure util / shear util / bearing util | 0.938 / 0.66 / 0.772 | | Δ_total, Δ_live | 0.339 in, 0.226 in | Corrected vs. the source PDF: the shear section label "acting bending stress" becomes "acting shear stress", and the blank page 3 is omitted. Corrections are noted in the sheet's Scope section. ## Conventions (inherited) - `lib/sheet.typ` is shared. It must not be modified except where an explicit user request authorizes an **additive-only** change (see the 2026-08-20 change: `check_service` added; `check` unchanged). - No existing calculation (`steel-beam`, `concrete-beam`, `concrete-beam2`, `shore-post`) may be modified. - Compile from `worksheets/` with `--root .`. - Lock the reference example in pytest with `pytest.approx` before treating the tool as stable. ## Milestones - 2026-08-19 — Task 001 DONE: `calc.py` + `input.yaml` + `results.json` created. All five checks pass for the default "Pompano Beach" input; values match the reference example (flexure 0.938, shear 0.66, bearing 0.772, Δ_total 0.339 in, Δ_live 0.226 in). See `TASKS.md`. - 2026-08-19 — Task 002 DONE: `test_wood_joist.py` created with 10 numerical lock tests; all pass (`10 passed`). - 2026-08-19 — Task 003 DONE: `beam.typ` created, compiled to `generated/wood-joist.pdf` (206 KB), and the Typst metadata test appended; all 11 tests pass (`11 passed`). - 2026-08-19 — Task 004 DONE: `README.md` and `codemap.md` refreshed with the wood-joist entry; all 11 tests still pass. - 2026-08-19 — Task 005 DONE: Reviewer PASS after one fix cycle. Issues found and fixed: (1) C_L ≤ 1.0 now explicitly enforced with regression test; (2) bearing applicability limits enforced (l_b < 6 in, end distance ≥ 3 in) with new `bearing_end_distance` input and tests. Final state: 14 tests pass, PDF compiles, results.json idempotent. Calculation approved. - 2026-08-20 — Tasks 006–008 DONE: load determination moved to Typst (mirroring steel-beam), `check_service` added to `lib/sheet.typ` (Acting/Allowed/ Utilization) and used for the two deflection checks, `input.yaml` switched to checked demands (`w`, `wL`, `M`, `V`, `R`), tests updated with the `` reconciliation test, and README/codemap refreshed. Reviewer PASS (all 8 checklist items, deterministic evidence verified); simplify pass clean. Final state: 15 tests pass, PDF compiles, results.json idempotent, benchmark values unchanged (flexure 0.938, shear 0.66, bearing 0.772, Δ_total 0.339 in, Δ_live 0.226 in). ## Final deliverables - `calcs/wood-joist/calc.py` — NDS 2018 ASD wood joist analysis (flexure with C_L, shear, bearing, deflection). CLI: `--input`, `--output`, `--stdout`. - `calcs/wood-joist/input.yaml` — Pint-quantity inputs; defaults reproduce the "Pompano Beach" reference example. - `calcs/wood-joist/test_wood_joist.py` — 14 tests locking the reference values plus validation guards and the Typst metadata query test. - `calcs/wood-joist/beam.typ` — presentation sheet (no recomputation), compiled to `calcs/wood-joist/generated/wood-joist.pdf`. - `README.md`, `codemap.md` — documented and indexed. ## Known limitations - Single-span, uniformly loaded, rectangular-section joist only. - No repetitive-member live-load reduction beyond the explicit `C_r` factor. - No lateral-torsional restraint beyond the single unbraced length `l_e`. - Deflection limits hardcoded to L/240 (total) and L/360 (live). ## Dependencies - Python: `pyyaml`, `pytest`, `pint` (already in `requirements.txt`). - `typst` CLI for compile and the metadata-query test. - No new third-party Python packages beyond `requirements.txt`.