Attribute schema

August 3, 2026 · View on GitHub

Every node carries identity fields. Only class-level nodes (6-digit leaves) carry the attribute payload — divisions and groups are structure, not data.

The attributes exist to answer two questions without opening the box:

  1. What else belongs next to this? → zone, access frequency, anti-affinity
  2. What happens to it in transit? → mass, density, fragility, hazard, orientation

Identity

FieldTypeRequiredNotes
idstringyes2, 4 or 6 digits. See id-scheme.md
namestringyesSingular-plural natural phrasing, e.g. "Pots & pans"
descriptionstringyesOne or two sentences. Written to be read by a classifier as much as a human — say what falls outside the class when the boundary is not obvious
aliasesstring[]noSearch and classifier hints: trade names, regionalisms, common misspellings
statusenumnoactive (default) | deprecated
superseded_bystringnoRequired when status: deprecated
notesstringnoFree text; anything the fields cannot carry

Physical

FieldTypeDefaultNotes
typical_mass_kgnumberrequiredMass of one representative item
bulk_density_kg_per_lnumberrequiredMass per litre as packed, voids included. This is the field that does the work — see below
nestableboolfalseItems of this class collapse into each other (pots, bowls, buckets)
stackablebooltrueCan be stacked on its own kind
load_bearingbooltrueOther things may be stacked on top of it
orientationenumanyany | upright | flat — how it must sit in the box

Why bulk density is the important number

Item mass tells you what one thing weighs. Bulk density tells you whether the box is liftable, which is the constraint that actually bites.

A 40 L box has a safe manual-handling ceiling around 20 kg — so 0.5 kg/L is the line. Anything denser than that cannot fill a 40 L box:

Class of thing~kg/L40 L box weighs
Duvets, pillows0.062 kg
Folded clothing0.156 kg
Plastic toys0.156 kg
Pots & pans (voids)0.2510 kg
Cables, coiled0.3514 kg
Paperbacks0.5020 kg ← at the limit
Hardbacks0.6024 kg
Hand tools0.6526 kg
Paper, documents0.7530 kg
Tinned food0.9036 kg
Screws & fixings, loose1.2048 kg

The classic packing mistake — a large carton filled with books — is one multiplication away from being caught. A system holding this field can refuse the fill before anyone lifts it.

All mass and density figures are estimates unless a notes entry cites a measurement. They are planning figures accurate to roughly ±30%, which is enough to keep a box under a weight limit and not enough to bill a shipper on. Where a class spans a wide range (Furniture especially), the figure is the middle of the common domestic range, not the mean of everything sold.

Handling & sensitivity

FieldTypeDefaultNotes
fragileboolfalseBreaks on the impacts a box normally sees
crushableboolfalseDeforms permanently under load without breaking — lampshades, hats, tubes of paint
moisture_sensitiveboolfalseDamaged by damp storage: paper, textiles, electronics
temperature_sensitiveboolfalseDamaged by an unconditioned loft or a container in summer: candles, vinyl, batteries, cosmetics, medicines
light_sensitiveboolfalseFades or degrades in daylight: photographs, textiles, some plastics

fragile and crushable are separate on purpose. Glassware is fragile and not crushable; a lampshade is crushable and not fragile. They call for opposite placements in a box — fragile items go padded and central, crushable items go on top with nothing above.

Safety

FieldTypeDefaultNotes
child_hazardboolfalseMust be stored out of reach of a small child
child_hazard_reasonsenum[][]choking | sharp | toxic | medicinal | burn | electrical | strangulation | crush | drowning | magnet | battery_ingestion
hazmatenumnonenone | explosive | flammable | corrosive | aerosol | oxidiser | toxic | compressed_gas | biohazard
un_classenumnoneUN/ADR hazard class — 1 | 2.1 | 2.2 | 2.3 | 3 | 4.1 | 4.2 | 4.3 | 5.1 | 5.2 | 6.1 | 6.2 | 7 | 8 | 9. See hazmat.md
un_indicativestringThe typical UN number where one entry dominates the class, e.g. UN1950. Unset means it varies by product
batteryenumnonenone | alkaline | button_cell | nimh | lithium_ion | lead_acid
perishableboolfalseHas a use-by horizon: food, some medicines, adhesives

hazmat and un_class are both required once either is set, and the validator enforces the mapping between them. They exist as a pair because they answer to different readers — hazmat is what the person packing the box understands, un_class is what a freight forwarder or customs agent needs. Neither is a shipping declaration; hazmat.md sets out the limits.

child_hazard is stored as the risk, not as its inverse. "Child safe" as a field would make the safe state the assertion, and an unfilled record would read as safe — the wrong way round for a field whose whole job is to catch the dangerous minority. Unset means unknown, and unknown should never resolve to safe.

battery is separated from hazmat because it is not a hazard classification, it is a shipping and storage one. Lithium cells are what make a shipment restricted, what makes a loft a bad idea, and what should never be packed charged and unterminated.

Logistics

FieldTypeDefaultNotes
storage_zoneenumrequiredkitchen | living | bedroom | bathroom | utility | garage | loft | basement | office | outdoor | any — where the class naturally lives
access_frequencyenumrequireddaily | weekly | monthly | seasonal | archive — drives shelf depth and box order
two_personboolfalseNot a one-person lift
disassemblyboolfalseNormally taken apart to move
handlingtag[][]Transport requirements that are not fragility. See special-handling.md
value_densityenumlowlow | medium | high — value per litre, for insurance declarations and for deciding what does not go in the van
mover_restrictedboolfalseCommercial removers routinely refuse to carry it
co_store_avoidtag[][]food | textiles | paper | electronics | medicine | bedding | children | heat

handling values: hand_carry, drain_required, depressurise_required, transit_restraint, settle_after_transit, professional_disconnect, specialist_mover, rigid_case, climate_controlled, customs_declaration, permit_required, living, magnetic.

fragile and handling do not overlap. fragile is a property of the object that calls for padding on the day; handling is a set of actions with lead times attached — an appointment to book, a tank to drain, a permit to apply for. Most of them have to happen before moving day rather than on it, which is the whole reason they are worth recording weeks in advance.

co_store_avoid is the anti-affinity list, and it is the only field that constrains combinations. Solvents do not go in with textiles; anything corrosive stays away from electronics; nothing chemical shares a box with food. An affinity engine that only clusters on similarity will happily put drain cleaner next to the flour, because both live in the kitchen and both are used monthly. This field is what stops it.

mover_restricted is a fact about the trade rather than about the object. Removal firms' standard terms exclude flammables, aerosols, compressed gas, open paint, and usually anything with a lead-acid battery, because their insurance excludes them. The item is not dangerous to store — it just cannot travel on their van, and it needs to be found before the day, not on it.

Inheritance

Attributes do not inherit down the tree. A group is not required to be homogeneous, and asserting a default at group level would quietly become the truth for classes nobody had got to yet. Every class states its own values, and absent optional fields take the documented defaults above.

Roll-ups go the other way: a division's characteristics are computed from its classes, weighted by whatever the caller has (item counts, volumes, masses), and are never stored.