calcs/wood-joist/PROJECT_STATE.md

207 lines
10 KiB
Markdown
Raw Normal View 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`.