swapit

Stateful, local-first household toxics inventory + swap engine. Identify the items in a home that carry endocrine disruptors and persistent chemicals (BPA/BPS, phthalates, PFAS/PTFE, parabens, flame r

SKILL.md

GitHub

swapit — household toxics inventory + swap engine

What this skill is

swapit turns a vague worry ("there's probably a lot of plastic and BPA in my house") into a concrete, prioritized, trackable plan. It is stateful: your household inventory lives on disk and persists across sessions. It is local-first: zero paid services, nothing leaves your device unless you explicitly opt into the (M3) commons.

One pass produces:

  1. An inventory of the items you own, each tagged with its generic item-class.
  2. A risk score per item that reflects actual exposure — a scratched non-stick pan used daily at high heat ranks far above an unopened plastic bin in the garage.
  3. A ranked "swap-first" list — the 5% of changes that cut the most exposure.
  4. Swap tracking — chosen alternative, procurement status, checklist, bookmarks.
  5. A shareable HTML report (and, in M2, a live dashboard at swapit serve).

The three realms (and the privacy invariant)

Realm What it holds Sharing
1 · Knowledge hazards → item-classes → alternatives (generic, cited facts) shareable; cached + (M3) commons-synced
2 · Inventory your items, rooms, quantities, brands, photos, swaps, bookmarks PRIVATE — never leaves the device
3 · Commons (M3) anonymized contributions enriching the shared knowledge graph opt-in, reviewed, generic facts only

Privacy invariant (binding): Realm-2 inventory (items, rooms, quantities, brands, photos, purchase info, location) never crosses the sync boundary. Only Realm-1 generic facts are shareable, via explicit opt-in + a reviewable swapit sync --dry-run. Enforced by an allowlist serializer (scripts/anonymize.py) on the client and a backstop on the commons server, plus fuzz tests asserting every real inventory item is rejected by the gate. Forbidden-field list: anonymize.CONTRIBUTION_FORBIDDEN (the inventory-structural subset of state.PRIVATE_FIELDS).

Data model

State lives at ~/.config/swapit/ (override with $SWAPIT_HOME; honors $XDG_CONFIG_HOME).

~/.config/swapit/
├── knowledge/   hazards.jsonl · item-classes.jsonl · alternatives.jsonl · products.jsonl · procurement.jsonl
├── inventory/   events.jsonl(append-only audit) · items.json · swaps.json · rooms.json · bookmarks.json
├── contributions/ queue.jsonl   (M3)
├── sync/        config.json · sync-log.jsonl   (M3)
└── photos/

Node schemas (see seed/*.jsonl for the full, cited dataset):

  • hazard — id · name · aliases · class · mechanism · exposure_routes · regulatory · severity(0-3) · evidence_strength · sources
  • item_class — id · name · category · description · hazards[]{hazard_id, presence_likelihood, rationale} · detection_hints · sources
  • alternative — id · name · replaces[] · material · rationale · tradeoffs · caveats · avoids_hazards · residual_concerns · sources
  • procurement_option (where-to-buy, public) — id · alternative · item_class · retailer · region(ISO-3166-1 a2) · area · url · price_min · price_max · currency · as_of · availability · confidence · corroboration_count
  • item (private) — id · name · item_class · room · quantity · brand · condition · usage{frequency, food_contact, heat, child_contact} · status · notes · photos
  • swap (private) — id · item_id · chosen_alternative · procurement{status, cost, vendor} · checklist[] · bookmarks[]

Risk model

risk = severity × presence_likelihood × evidence × exposure_relevance × frequency × condition
  • exposure_relevance activates a hazard's route against how the item is used (food-contact + heat maxes out an ingestion/food-contact-heat hazard; dermal matters for personal care; inhalation for cleaning/furniture; child-contact amplifies).
  • Item score = scaled sum of per-hazard risk → band high / medium / low.
  • This is the prioritization intelligence — the analogue of procurer's "dominant failure mode": fix the few items that drive most of the exposure first, not a guilt list of everything plastic.

Modes

Mode What it does
init create state + load the seed knowledge graph
add add a household item (name, class, room, condition, usage) → prints its assessment
assess assess an item or an ad-hoc item-class → hazards + risk + ranked alternatives
list list inventory, filter by --room/--band/--status/--class/--hazard, sort by risk
swap create/update a swap plan: choose alternative, set status, checklist, bookmark, cost
score household exposure summary + the swap-first ranking
report generate the self-contained HTML report (Category-C)
procure procurer handoff brief + known where-to-buy offers for the swap target (filter by --region); record a found offer (--retailer/--url/--price-*) to the public commons
knowledge browse/search the knowledge graph (list/search/show)
rooms list/add rooms
selfheal validate knowledge edges + inventory refs + grounding; exit non-zero on errors
serve live local dashboard at http://127.0.0.1:8731 — kanban board, checklist, bookmarks, status; every click writes back to state
contribute queue an anonymized fact — product / hazard (item-class→hazard) / alternative / procurement (public where-to-buy offer) / item-class (new taxonomy node); applied locally + gated for the commons
sync push the contribution queue + pull community knowledge (opt-in); --dry-run previews exactly what would be sent; --configure sets the endpoint

Typical flow

swapit init
swapit add --name "Old Teflon pan" --class nonstick-cookware --room kitchen \
           --condition scratched --frequency daily --food-contact --heat
swapit score                      # what to swap first
swapit swap itm_xxxx --to cast-iron-skillet --status sourcing --add-task "buy 10in skillet"
swapit procure itm_xxxx           # -> hand the brief to the `procurer` skill
swapit report --open              # shareable HTML

Compounding with other skills

  • procurer — swapit procure <item> builds the need ("replace 3 polycarbonate bottles with glass/steel") and the ready procurer prompt; procurer returns cited sources + a budget envelope.
  • bookkeeping (P6) — a novel, durable hazard/product finding (e.g. a newly characterized item-class) is filed proactively into research/entities/ and can later flow to the commons.
  • health — personal exposure context. content-creation — educational posts from the graph.

Grounding discipline

The seed knowledge cites only authoritative bodies (NIEHS, ATSDR/CDC, US EPA, US FDA, ECHA/EU REACH, CA OEHHA Prop 65, WHO, EWG). Every record carries ≥1 source; verified: false marks these as reference-grade (not freshly fetched) — run a sourced pass (or the commons) to refresh. selfheal fails if any node loses its citation. This is consumer guidance grounded in public-health science, not medical or legal advice.

Resources

  • scripts/swapit.py — CLI entrypoint + command handlers
  • scripts/state.py — two-realm state layer (PRIVATE_FIELDS defines the sync boundary)
  • scripts/ops.py — the single state-mutation write path (shared by CLI + dashboard)
  • scripts/knowledge.py — knowledge graph load + edge resolution
  • scripts/risk.py — exposure-risk scoring engine
  • scripts/report.py — self-contained HTML report generator (Category-C)
  • scripts/server.py — swapit serve live dashboard (stdlib http.server, localhost-only)
  • scripts/anonymize.py — the privacy gate: allowlist fact builders + the forbidden-field scan
  • scripts/sync.py — commons sync client (queue, --dry-run preview, push/pull, merge)
  • scripts/selfheal.py — integrity validator
  • templates/dashboard.html — the live dashboard (inline CSS/JS, Category-C)
  • commons/ — the networked commons reference server (FastAPI + SQLite; deploy gated)
  • seed/{hazards,item-classes,alternatives}.jsonl — grounded starter knowledge
  • tests/ — risk, knowledge, self-heal, CLI, privacy/anonymize, sync, report, and server tests

Collaboration (the commons)

Contributions are generic facts only — swapit contribute product|hazard|alternative|procurement|item-class builds an anonymized fact (a public product→item-class mapping, a hazard-edge correction, an alternative, a where-to-buy offer, or a new taxonomy node), applies it locally, and queues it. swapit sync --dry-run shows exactly what would be sent; swapit sync pushes the queue and pulls community knowledge (opt-in, after --configure). Identical facts from different users corroborate (content-addressed) rather than duplicate; the commons serves a fact once two distinct contributors corroborate it (corroboration >= 2) — moderation is corroboration-gated, not confidence-gated, because confidence is caller-supplied and a lone submitter could otherwise self-approve. The privacy invariant is enforced on both sides (client anonymize gate + server backstop) — inventory never crosses.

Where-to-buy (procurement) commons

A procurement_option fact is a public offer: a safer alternative sold by a retailer in a region (ISO-3166-1 alpha-2), with optional url, price_min/price_max + currency + as_of, area, and availability. It is content-addressed on (alternative, retailer, region) — the same offer corroborates across users; a different region is a different fact (the geographic scale axis). Price/url/area are refinable market data: a corroboration with a strictly newer as_of freshens the price forward, never regressing fresher data. The privacy seam is sharp — the public offer uses retailer + price_* and never the private vendor/cost (where you bought and what you paid stay in Realm 2). swapit procure <item> --region <CC> surfaces known offers and records new ones you find — growing an open, detailed, geo-scoped dataset anyone can use.

Roadmap

  • M1 (shipped, 0.1.0) — data model, seed knowledge, CLI, risk engine, static HTML report, self-heal.
  • M2 (shipped, 0.2.0) — swapit serve: live local dashboard with read/write back to state.
  • M3 (0.3.0) — anonymized collaboration + networked commons (commons/, FastAPI + SQLite). Privacy invariant enforced by allowlist serialization + fuzz tests on both client and server.
  • M4 (this release, 0.4.0) — geo-scaled procurement commons + taxonomy growth: the procurement_option (public where-to-buy, keyed by (alternative, retailer, region), forward-only price freshening) and item_class (corroboration-gated taxonomy growth) fact kinds; procure surfaces + records offers; seed offers across US/CO/DE/GB; cross-language hash parity locked by pinned vectors. Live deploy to broomva.tech infra is gated on explicit go (creds/DNS); the skill is fully functional offline without it.