# Model Metrics

*Volume, mass, area, enclosed volumes, and a bill of materials*

**Names:** `volume` `no_join` `a` `b` `reason`

## When to use

Use `ww metrics <file>` to ask what a design weighs, how much surface a finish must cover, what
its box holds, and what to buy. The numbers are read from the built solids, so they follow every
parameter. Add a `volume` node when you need an **enclosed volume**: the litres inside a speaker
cabinet, a chest, a drawer, a sealed compartment. Metrics are a report: they never change the
model.

## Syntax

```text
volume Interior {
    bounded_by End_L End_R Front Back Bottom Lid
    subtract Shelf
}
```

- `volume <Name>` is a top-level node of the project. `Name` keys the result in `metrics.json`.
- `bounded_by` lists the parts whose union closes the region (part ids, or any unique trailing
  run of one: `Lid`, `Chest/Lid`). At least one.
- `subtract` lists parts standing in the air that displace it (a shelf, a brace, a driver).
- Nothing else goes inside `volume`; it takes no properties.

CLI: `ww metrics <file> [--json] [--set name=value] [--waste F] [--coats N] [--end-grain-max X]`.
The fabrication pack carries the same report as `metrics.json`.

**Contact completeness** (issue #61 Stage 1). `ww metrics` finds every real planar contact
between built parts (exact geometry, not bounding boxes) and warns
(`ww::analysis::unaccounted_contact`) on any contact with no covering `join` and no covering
`no_join`:

```text
no_join a=Shelf b=Case reason="rests on cleats, lifts out, no joint by design"
```

- `no_join` is a top-level node of the project, like `volume`.
- `a=` and `b=` name the two touching parts the same way `volume`'s `bounded_by` does: an exact
  id, or any unique trailing run of one. Both are required.
- `reason=` is required and must not be empty — the escape hatch names *why* a real contact has
  no joint, it does not silence by merely existing.
- This decides no joint type and changes no solid (D8 stands): it only asks whether the author
  said *something* about a touching pair.

**Finish quantity.** Give a material or part a `finish="'danish oil'"`; `ww metrics` matches the
text to `docs/finishes` and, where that entry has a sourced coverage, reports litres as a range
(`finish.groups.<text>.litres`). Entries with no sourced figure (shellac, paints, milk paint)
report no quantity and say why.

**Bill of materials.** `bom` lists stock to buy per material and thickness (nominal, and with a
waste factor: `--waste F`, default 0.15; board feet for timber), purchased items split into
`fastener` (hardware with a `length`), `insert` (generated dowels, biscuits, dominos) and
`hardware`, and consumables: glue (the area of the glue lines) and finish litres. Give
hardware a `part_no=`, `supplier=` and `price=` and they appear in the list and the total.

## Example

```kdl
project name="Box" units=mm schema_version=1 {
    materials { material Oak type=hardwood }
    component Plate { stock Oak 120.0 100.0 10.0 }
    component Side  { stock Oak 10.0 100.0 60.0 }
    component Rail  { stock Oak 100.0 10.0 60.0 }
    assembly Chest {
        part Bottom instance=Plate { pos x=0.0 y=0.0 z=-70.0 }
        part Top    instance=Plate { pos x=0.0 y=0.0 z=0.0 }
        part Left   instance=Side  { pos x=0.0 y=0.0 z=-10.0 }
        part Right  instance=Side  { pos x=110.0 y=0.0 z=-10.0 }
        part Front  instance=Rail  { pos x=10.0 y=0.0 z=-10.0 }
        part Back   instance=Rail  { pos x=10.0 y=90.0 z=-10.0 }
    }
    volume Inside { bounded_by Bottom Top Left Right Front Back }
}
```

`Inside` is 100 x 80 x 60 = 480 000 mm³ = 0.48 L (`volumes.Inside.net_l`).

## Pitfalls and diagnostics

- **A leak is an answer, not a guess.** If the bounding parts do not close (a missing panel, an
  open driver cutout), `status` is `leak` and there is no volume. `leak_closes_at_mm` says how
  wide a seam would have to be thickened to close it; absent means a face is missing.
- **Seams under 2 micrometres** are sealed and flagged: `sealed_gap_mm` and
  `seal_error_bound_mm3` say how much the volume might be short.
- **Closed dowel sockets are not the enclosure**: chambers under 1 mL or under 1% of the
  bounding box are listed as `ignored_voids`, not counted.
- **Mass is `null` where the material has no known density**, with a reason: never a default.
  Give the material `species="white-oak"` or `density_kg_m3=...`. Mass is air-dry, about 12%
  moisture content.
- **Contact is planar only**: round tenons in round mortises are not found as contact, so free
  area there is a slight over-statement.
- **Finish litres are a range, and the end-grain multiplier (default x2 high end) is an
  assumption**: no maker figure for end grain is recorded. Curved end grain is not detected.
  Never trust a finish total with `area_without_litres_m2` above zero as the whole job.
- **Glue is an area, not a quantity.** No spread rate is recorded for any glue, so none is
  invented. Joined pairs that meet over a curved surface are counted in `pairs_without_contact`
  and left out of the area.
- **Stock totals are blanks, not a board layout**: waste is a flat factor, and a ring blank
  (sized by its circumscribing square) over-counts. Only a hardware entry with a `length` is
  listed as a fastener.
- **Unpriced items are counted, never priced at zero**: read `unpriced_items` beside
  `priced_total`.
- A `role=reference` part is reported but is in no total and cannot bound a volume.
- **A real contact with no `join` and no `no_join` is `ww::analysis::unaccounted_contact`.**
  This can flood a design that has many incidental true contacts by its own nature (a woven
  seat's warp/weft crossings, a veneer top's leaf-to-leaf edges) — each is a real finding, not
  a bug in the check, but excusing hundreds of such pairs one `no_join` at a time is often not
  the right fix; it is a sign the design's structure is not well served by a per-pair
  declaration and is worth raising, not silencing.
- A `no_join` naming a part that does not resolve is reported, not silently ignored, and the
  contact it was meant to excuse is still flagged.

## See also

- `docs/adr/0036-model-metrics.md`: the method, accuracy, and the JSON schema
- `docs/KDL_AUTHORING.md`, the `volume` section
- `docs/proposals/0132-derived-quantities-and-linked-parameters.md` section 3

## Names

Every name this page declares, how the example library uses it, and the shortest real use.

### `volume`

project-level node; node in 2 of 116 examples (2 uses); property in 2 of 116 examples (2 uses).

Shortest real use, from `six_board_chest.kdl`:

```kdl
volume Interior {
    bounded_by End_L End_R Front Back Bottom Lid
}
```

### `no_join`

project-level node; not used by any example yet.

### `a`

word-valued property; not used by any example yet.

### `b`

word-valued property; not used by any example yet.

### `reason`

word-valued property; not used by any example yet.

