plant: private Cargill Gouda tracker (/trackers/cargill)
All checks were successful
Deploy production / deploy (push) Successful in 1m19s
All checks were successful
Deploy production / deploy (push) Successful in 1m19s
Owner-only tracker for learning the Cargill Bioindustrial plant in Gouda: schema plant (entries, links with confidence, pending refs), plant.apply_entries bulk import, overview with clusters and flow diagram, kind tabs, glossary, questions, detail pages and owner inline editing. Docs in docs/plant-tracker.md.
This commit is contained in:
@@ -34,3 +34,4 @@ export * from './worldtodo';
|
||||
export * from './osu-goals';
|
||||
export * from './notes';
|
||||
export * from './math';
|
||||
export * from './plant';
|
||||
|
||||
118
src/lib/server/db/schema/plant.ts
Normal file
118
src/lib/server/db/schema/plant.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import {
|
||||
boolean,
|
||||
check,
|
||||
index,
|
||||
integer,
|
||||
text,
|
||||
uniqueIndex,
|
||||
type AnyPgColumn
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { plantSchema } from './schemas';
|
||||
import { id, timestamps, timestamptz } from './helpers';
|
||||
|
||||
// Cargill plant tracker (/trackers/cargill, owner-only): what the Cargill Bioindustrial plant
|
||||
// does. One generic `entries` table (departments, products, process steps, equipment, glossary,
|
||||
// questions, notes, roles) plus typed `links` between them. Bots apply JSON with
|
||||
// `select plant.apply_entries('{...}'::jsonb)` (shape documented in the migration and in
|
||||
// docs/plant-tracker.md). Kinds, confidences and relations are kept in sync with #lib/plant.
|
||||
|
||||
export const plantEntries = plantSchema.table(
|
||||
'entries',
|
||||
{
|
||||
id: id(),
|
||||
/** process_step | product | department | equipment | glossary | question | note | person_role */
|
||||
kind: text('kind').notNull(),
|
||||
/** Unique per kind; used in URLs and by apply_entries. */
|
||||
slug: text('slug').notNull(),
|
||||
title: text('title').notNull(),
|
||||
summary: text('summary'),
|
||||
/** Markdown. */
|
||||
body: text('body'),
|
||||
parentId: integer('parent_id').references((): AnyPgColumn => plantEntries.id, {
|
||||
onDelete: 'set null',
|
||||
onUpdate: 'cascade'
|
||||
}),
|
||||
tags: text('tags')
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`'{}'::text[]`),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
/** Where it came from, e.g. "page 2026-10-26", "Walter White", "Stan". */
|
||||
source: text('source'),
|
||||
/** confirmed (checked at work) | assumed (from the guide) | open (unknown). */
|
||||
confidence: text('confidence').notNull().default('assumed'),
|
||||
/** Questions: when it was answered (null = still open). */
|
||||
answeredAt: timestamptz('answered_at'),
|
||||
...timestamps
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex('entries_kind_slug_key').on(t.kind, t.slug),
|
||||
index('entries_parent_idx').on(t.parentId),
|
||||
check(
|
||||
'entries_kind_check',
|
||||
sql`${t.kind} IN ('process_step','product','department','equipment','glossary','question','note','person_role')`
|
||||
),
|
||||
check('entries_confidence_check', sql`${t.confidence} IN ('confirmed','assumed','open')`),
|
||||
check('entries_slug_check', sql`${t.slug} ~ '^[a-z0-9]+(-[a-z0-9]+)*$'`)
|
||||
]
|
||||
);
|
||||
|
||||
export const plantLinks = plantSchema.table(
|
||||
'links',
|
||||
{
|
||||
id: id(),
|
||||
fromId: integer('from_id')
|
||||
.notNull()
|
||||
.references(() => plantEntries.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
toId: integer('to_id')
|
||||
.notNull()
|
||||
.references(() => plantEntries.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
/** feeds_into | made_in | uses | part_of | related */
|
||||
relation: text('relation').notNull(),
|
||||
/** Short edge label, e.g. "fatty acids". */
|
||||
label: text('label'),
|
||||
/** Drawn dashed in the flow (optional path or a loop back); doesn't push layers down. */
|
||||
dashed: boolean('dashed').notNull().default(false),
|
||||
/** confirmed | assumed | open; not-confirmed links are drawn dashed with a "?". */
|
||||
confidence: text('confidence').notNull().default('confirmed'),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow()
|
||||
},
|
||||
(t) => [
|
||||
check('links_confidence_check', sql`${t.confidence} IN ('confirmed','assumed','open')`),
|
||||
uniqueIndex('links_from_to_relation_key').on(t.fromId, t.toId, t.relation),
|
||||
index('links_to_idx').on(t.toId),
|
||||
check(
|
||||
'links_relation_check',
|
||||
sql`${t.relation} IN ('feeds_into','made_in','uses','part_of','related')`
|
||||
),
|
||||
check('links_not_self_check', sql`${t.fromId} <> ${t.toId}`)
|
||||
]
|
||||
);
|
||||
|
||||
/** Parents and links whose target isn't in the tracker yet. apply_entries retries them on every
|
||||
* run, so a later batch that adds the target resolves them; the detail page lists them as pending. */
|
||||
export const plantPending = plantSchema.table(
|
||||
'pending',
|
||||
{
|
||||
id: id(),
|
||||
fromId: integer('from_id')
|
||||
.notNull()
|
||||
.references(() => plantEntries.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
/** "parent" or a link relation. */
|
||||
field: text('field').notNull(),
|
||||
/** The reference as given ("slug" or "kind:slug"). */
|
||||
toRef: text('to_ref').notNull(),
|
||||
label: text('label'),
|
||||
dashed: boolean('dashed').notNull().default(false),
|
||||
confidence: text('confidence').notNull().default('confirmed'),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow()
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex('pending_from_field_ref_key').on(t.fromId, t.field, t.toRef),
|
||||
check(
|
||||
'pending_field_check',
|
||||
sql`${t.field} IN ('parent','feeds_into','made_in','uses','part_of','related')`
|
||||
)
|
||||
]
|
||||
);
|
||||
@@ -30,3 +30,4 @@ export const moviesSchema = pgSchema('movies');
|
||||
export const worldtodoSchema = pgSchema('worldtodo');
|
||||
export const notesSchema = pgSchema('notes');
|
||||
export const mathSchema = pgSchema('math');
|
||||
export const plantSchema = pgSchema('plant');
|
||||
|
||||
Reference in New Issue
Block a user