# Plant tracker (`/trackers/cargill`) A private (owner-only) knowledge base about the Cargill Bioindustrial plant in Gouda: departments, products, process steps, equipment, glossary terms, questions, notes and roles (never colleague names). Logged-out visitors get a 404 on the page, its data and its actions (`ownerOnlyPrefixes` in `src/lib/server/owner.ts`). ## Schema `plant` (migration `drizzle/20261026133106_plant`) - `plant.entries`: `kind`, `slug` (unique per kind), `title`, `summary`, `body` (markdown), `parent_id` (any kind, e.g. a note under a note), `tags`, `sort_order`, `source`, `confidence` (`confirmed` | `assumed` | `open`), `answered_at` (questions), timestamps. - `plant.links`: `from_id`, `to_id`, `relation` (`feeds_into`, `made_in`, `uses`, `part_of`, `related`), optional `label`, `dashed`, `confidence` (default `confirmed`; `assumed` or `open` links show dashed in the flow and with a "?" badge on entry pages). - `plant.pending`: parents and links whose target doesn't exist yet. Every `apply_entries` run retries them, so a later batch that adds the target resolves them. Entry pages list them as "pending". Kinds: `department`, `product`, `process_step`, `equipment`, `glossary`, `question`, `note`, `person_role`. How tags drive the UI: - `cluster` (departments): a section in the departments tab and a card in the overview, with every unit below it (units with `parent: "cluster-ea"`, and their own units, like MP1's). - `inactive`: dimmed everywhere; the "hide inactive" switch hides them (off by default). - `overview` (notes): shown at the top of the overview. ## Bulk import: `plant.apply_entries(jsonb)` Upserts by `(kind, slug)`, then sets parents and links by slug. Run it as the app user or the owner; for big files pipe SQL through stdin, e.g. `select plant.apply_entries($doc$ { ... } $doc$::jsonb);` The argument is either an object `{ "source": ..., "entries": [...], "links": [...], "delete": [...] }` or a bare array of entries. ```json { "source": "Walter White, batch 5 (2026-10-26)", "entries": [ { "kind": "department", "slug": "414-kolom-1", "title": "414 Kolom 1", "summary": "Fat splitting column, continuous", "body": "In and out:\n- In: feed, water and high-pressure steam.\n- Out: fatty acid and glycerine water.", "parent": "cluster-dim", "tags": ["splitting", "continuous"], "confidence": "confirmed", "links": [{ "to": "splitting", "relation": "uses", "label": "optional" }] }, { "kind": "question", "slug": "rndm", "title": "What is RNDM?", "tags": ["answered"] } ] } ``` - Required: `kind`, plus `slug` or a `title` to slugify; new entries need a `title`. - Optional: `summary`, `body`, `parent` (alias `parent_slug`; `null` clears it), `tags`, `confidence` (default `assumed`, `open` for questions), `sort_order`, `source`, `answered`, `replace_links`, `replace_tags`, `body_mode` (`"append"`), `links` (`to`, `relation`, `label`, `dashed`, `confidence`, `remove`). - Updating an existing entry: - Keys that are left out keep their current value. An empty `summary` or `body` also keeps the current text. With `"body_mode": "append"` the new body is added below the current one (skipped when it is already there) instead of replacing it. - `tags` are merged into the existing tags, unless the entry sets `"confidence": "confirmed"` or `"replace_tags": true`, which replace them. - The top-level `source` is only the default for new entries; pass `source` on an entry to overwrite it. - A question tagged `answered` (or with `"answered": true`) is marked answered; the tag is dropped. - References (`parent`, `to`, `from`) are a slug or `kind:slug`. A bare slug used by several kinds resolves in this order: process_step, product, department, equipment, note, question, person_role, glossary (so `splitting` is the process step). - Unknown references never abort the import: they are listed in `unresolved` and kept in `plant.pending` until their target exists. Returns `{ inserted, updated, linked, unlinked, deleted, unresolved: [{from, field, to}], resolved_pending, pending, entries: [{id, kind, slug, action}] }`.