calcs/wood-joist/PROJECT_STATE.md
smillmorel d5ac3fca7e Add structural calculation worksheets
Collection of engineering calculation projects (Python + Typst), each with
input, calc script, tests, results, and generated PDF where available.
2026-09-21 12:19:20 -04:00

10 KiB
Raw Blame History

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 <wood-joist-loads> 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 <wood-joist-loads> 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.