# TODO — one unit inside, feet-and-inches out Decision: **the whole project moves to inches internally, and every number shown to a person is formatted `xx'-xx"`.** Not started. Will says when. Measured scope, 2026-08-27: 237 occurrences of `_ft` across `panellift/src` (151) and `WillTools.extension/lib/vibe` (86). `length_ft` appears 68 times and `width_ft` 52 — those two are the `Panel` model that the statics, the job-file contract and 175 tests are all written against. **Why this is worth doing carefully.** The most expensive incident this project has had was a 12x unit error: an inch value fed to `XYZ()`, which takes feet, put copies 6,500 ft away and manufactured a week of false symptoms. A refactor whose whole purpose is to remove unit ambiguity is exactly the place to reintroduce it. Every step below is ordered so a mistake shows up as a failing test rather than as a wrong number in a report. --- ## Step 0 — the output formatter, first and on its own Do this before touching any internals; it is pure gain and it stands alone. - `ticketqc.fmt_feet` / `fmt_inches` / `parse_feet` / `parse_inches` already exist. Move them somewhere both halves can import — they are currently only reachable from the Revit side. - Every number a person reads goes through it: findings, reports, the HTML, the config dump's comments. A raw `18.9167` never reaches a page again. - Round-trip test: `parse(fmt(x)) == x` to the nearest 1/16", across a range that includes the values this job actually uses. ## Step 1 — make every name state its unit, truthfully Cheap, mechanical, and it removes the bug class on its own. - `read_ticket` returns `thickness` as **0.667, which is feet**, under a name that reads as inches. Fix that and audit every sibling field the same way. - Any field whose name carries no unit gets one. `length`, `width`, `thickness` are the offenders. - After this step the codebase is still in feet, but nothing lies about it. ## Step 2 — the boundary, not the core Revit's API returns feet. Convert **once**, at the point of reading, and never again: - `liftexport.export` and `ticketqc.read_ticket` are the two doors. Everything the model hands them is feet; everything they hand on is inches. - Do NOT sprinkle `* 12` through the callers. One conversion per door, named. ## Step 3 — the job-file contract `schema: "panellift/1"` carries `length_ft`, `width_ft`, `thickness_in`, `extra_cast_ft3` and more. There are 184 saved job files in the sweep directory and they must keep working. - Bump to `panellift/2` with inch fields. - `cli.read_panel` accepts both: a `panellift/1` file is converted on read. Keep that reader until the saved files are regenerated, and test it against a real `panellift/1` file, not a synthetic one. ## Step 4 — panellift's core, last `Panel.length_ft` -> `Panel.length_in`, and the 151 sites that follow from it. - The statics were validated against published PCI examples. Convert the model and the solver in one commit, and require the PCI example tests to pass unchanged — they are the acceptance criterion for this step. - `pci.py`'s published constants stay in whatever unit the source uses; convert at the call, not in the table. - Watch the compound units: `unit_weight_pcf` is per cubic FOOT, `extra_cast_ft3` is cubic feet, section modulus is in3, stresses are psi. Only lengths move. A blanket rename would break the ones that are already inches. ## Step 5 — sweep the defaults `group_tolerance_ft` (28 occurrences), `min_edge_distance_in`, `transfer_length_in` and the rest live in the knowledge file, where a plant has already set values. Changing a key's unit silently reinterprets a number somebody chose. - Rename the key when the unit changes, so an old key is a *missing* key rather than a wrong one, and `config_problems` reports it. - Never leave a key whose name kept its unit but whose meaning changed. --- ## Acceptance - panellift and vibe test suites green, including the PCI example cases. - One `panellift/1` job file from the sweep directory still analyses, and produces the same numbers it produced before the refactor. - `grep -rE "[a-z_]+_ft\b"` over `panellift/src` returns only genuinely cubic-foot or per-cubic-foot names. - No report anywhere prints a bare decimal length.