Integration pathways

August 2, 2026 · View on GitHub

Written against a Homebox-derived inventory system, but nothing below depends on Homebox specifically — the requirements are a table of items, somewhere to put a foreign key, and a way to run a classification job.

Design rule: one column, one join

The taxonomy is reference data. It should be loaded, not copied.

The whole integration is one nullable column on the item table holding a 6-digit class code, plus a table holding the taxonomy itself. Do not denormalise the attributes onto items. Copying fragile and bulk_density_kg_per_l onto every row means the day the taxonomy is corrected, every historical row keeps the old answer, and you have quietly created a second source of truth that drifts.

The one exception is anything a user has explicitly overridden — see Overrides, below.

Load dist/taxonomy.csv into a table and add a foreign key.

CREATE TABLE hg_class (
  id                    char(6) PRIMARY KEY,
  name                  text NOT NULL,
  parent                char(4) NOT NULL,
  division_id           char(2) NOT NULL,
  description           text NOT NULL,
  typical_mass_kg       numeric(8,3) NOT NULL,
  bulk_density_kg_per_l numeric(6,3) NOT NULL,
  fragile               boolean NOT NULL DEFAULT false,
  crushable             boolean NOT NULL DEFAULT false,
  child_hazard          boolean NOT NULL DEFAULT false,
  child_hazard_reasons  text[]  NOT NULL DEFAULT '{}',
  hazmat                text    NOT NULL DEFAULT 'none',
  battery               text    NOT NULL DEFAULT 'none',
  mover_restricted      boolean NOT NULL DEFAULT false,
  storage_zone          text    NOT NULL,
  access_frequency      text    NOT NULL,
  value_density         text    NOT NULL DEFAULT 'low',
  co_store_avoid        text[]  NOT NULL DEFAULT '{}',
  status                text    NOT NULL DEFAULT 'active'
  -- remaining columns per docs/schema.md
);

ALTER TABLE items
  ADD COLUMN hg_class_id      char(6) REFERENCES hg_class(id),
  ADD COLUMN hg_confidence    real,      -- 0..1 from the classifier
  ADD COLUMN hg_classified_at timestamptz,
  ADD COLUMN hg_source        text;      -- 'ai' | 'human' | 'rule' | 'import'

CREATE INDEX ON items (hg_class_id);
CREATE INDEX ON items (left(hg_class_id, 2));  -- division roll-up

hg_source and hg_confidence are not optional extras. Without them you cannot distinguish a code a person chose from one a model guessed at 0.4, and you will not be able to re-run the classifier later without destroying human corrections.

Refresh is a re-import: the file is small, codes are stable and never reused, so an upsert on id is safe and no item row needs touching.

Pathway B — read the JSON at runtime

For an app that would rather not own a table: ship dist/taxonomy.json, load it into memory at boot, keep the code on the item and resolve attributes in the application layer.

Simpler, and it forfeits the ability to filter or aggregate by attribute in SQL — which is most of the point. Reasonable for a mobile or offline client, wrong for the primary system.

Pathway C — MCP tool surface

An inventory system already exposing MCP gains most from three tools, and they should be separate calls rather than one overloaded one:

ToolTakesReturns
classify_itemname, description, photoranked candidate codes with confidence
get_classcodethe full attribute record
check_containercontainer idroll-up: mass, fragile %, hazards, conflicts

classify_item should return candidates, not a decision. A tool that returns one code invites the caller to write it to the database; a tool that returns three with confidences invites the caller to apply a threshold, which is the behaviour you want.

The classification pipeline

Assignment is an LLM job. The taxonomy is designed for it: class description fields state boundaries explicitly ("Table knives only — kitchen knives are 100602") and aliases carry the vocabulary people actually use.

A workable prompt shape:

  1. Retrieve ~20 candidate classes by embedding or trigram match on name + aliases + description. Do not put all 84 — eventually several hundred — classes in the prompt; recall does not need it and precision suffers.

  2. Ask for the best code plus confidence plus a one-line reason, from the candidate list only.

  3. Apply thresholds:

    ConfidenceAction
    ≥ 0.85write the code, hg_source = 'ai'
    0.5 – 0.85write it, flag for review
    < 0.5write 9999, queue for a human

9999 is a feature. A classifier that must always choose will put the drain cleaner somewhere, and a wrong safety flag is worse than an absent one. A populated 9999 bucket is a work queue; a confidently wrong code is a lie the system will act on.

Where a photo is available, use it. Material and construction — the things that drive density and fragility — are visible and are frequently absent from the text description.

Cost and batching

Classification is a bulk one-off followed by a trickle. For a first pass over an existing inventory, batch 25–50 items per call against a shared candidate list drawn from the whole batch; per-item calls cost roughly an order of magnitude more for no accuracy gain. New items thereafter classify one at a time on creation, where latency matters more than throughput.

Overrides

Class attributes are defaults. An individual item may legitimately deviate — an unusually heavy pan, a book that is a first edition.

Store overrides as a sparse JSON column on the item (hg_overrides), not as a full copy of the attribute set, and resolve at read time as coalesce(item.override, class.value). Sparse means a taxonomy correction still reaches every item that has not explicitly disagreed with it.

An override is also a signal. Several items in the same class overriding the same field the same way means the class value is wrong and should be fixed upstream.

Versioning

Pin the taxonomy version you classified against. Codes never change meaning and never get reused, so a pinned version and a current version are always reconcilable — but a class that was deprecated and superseded needs a migration, and you cannot write one without knowing where you started.

Record the version in the item row or in a job log. Deprecations are the only breaking change the scheme permits, and superseded_by gives the migration path mechanically.

Order of work

  1. Load the reference table. Nothing else can start.
  2. Add the columns to items. Leave them null; nothing breaks.
  3. Classify one division's worth of items by hand — 50 or so — and keep it as a gold set.
  4. Run the classifier over the same items and measure. Under about 80% top-1 agreement, the problem is almost always the class descriptions, not the model.
  5. Bulk classify. Work the 9999 queue.
  6. Only then build anything that consumes the flags. Roll-ups computed over a half-classified inventory produce confident, wrong numbers, and people stop trusting the feature before it works.