--- phase: 04-pdf-ingest plan: 01 type: execute wave: 1 depends_on: [] files_modified: - teressa-copeland-homes/src/lib/db/schema.ts - teressa-copeland-homes/src/lib/db/index.ts - teressa-copeland-homes/scripts/seed-forms.ts - teressa-copeland-homes/seeds/forms/.gitkeep - teressa-copeland-homes/package.json autonomous: true requirements: - DOC-01 - DOC-02 must_haves: truths: - "form_templates table exists in the database with id, name, filename, createdAt, updatedAt columns" - "documents table has filePath and formTemplateId columns added" - "Running npm run seed:forms reads seeds/forms/ directory and upserts rows into form_templates" - "seeds/forms/ directory exists and is tracked in git (via .gitkeep)" artifacts: - path: "teressa-copeland-homes/src/lib/db/schema.ts" provides: "formTemplates table definition and updated documents table" contains: "formTemplates" - path: "teressa-copeland-homes/scripts/seed-forms.ts" provides: "CLI seed script for populating form_templates from seeds/forms/" exports: [] - path: "teressa-copeland-homes/seeds/forms/.gitkeep" provides: "Tracked placeholder for manually-downloaded PDF seed files" key_links: - from: "scripts/seed-forms.ts" to: "seeds/forms/" via: "node:fs/promises readdir" pattern: "readdir.*seeds/forms" - from: "scripts/seed-forms.ts" to: "src/lib/db/schema.ts formTemplates" via: "drizzle insert onConflictDoUpdate" pattern: "onConflictDoUpdate" --- Extend the database schema with a form_templates table and add missing columns to the documents table. Create the seed directory structure and seed script that fulfills the monthly-sync requirement (DOC-02) via manual re-run. Purpose: Every subsequent Phase 4 plan depends on this schema and the seed mechanism. Getting the data layer right first prevents downstream rework. Output: Updated schema.ts, new Drizzle migration applied, seed script at npm run seed:forms, seeds/forms/ directory tracked. @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-pdf-ingest/04-CONTEXT.md @.planning/phases/04-pdf-ingest/04-RESEARCH.md ```typescript import { pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core"; export const documentStatusEnum = pgEnum("document_status", ["Draft","Sent","Viewed","Signed"]); export const clients = pgTable("clients", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), email: text("email").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); export const documents = pgTable("documents", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), clientId: text("client_id").notNull().references(() => clients.id, { onDelete: "cascade" }), status: documentStatusEnum("status").notNull().default("Draft"), sentAt: timestamp("sent_at"), createdAt: timestamp("created_at").defaultNow().notNull(), }); ``` Task 1: Add formTemplates table and extend documents table in schema.ts teressa-copeland-homes/src/lib/db/schema.ts teressa-copeland-homes/seeds/forms/.gitkeep Edit teressa-copeland-homes/src/lib/db/schema.ts: 1. Add `integer` to the drizzle-orm/pg-core import. 2. Add the `formTemplates` table ABOVE the `documents` table (must precede its reference): ```typescript export const formTemplates = pgTable("form_templates", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), filename: text("filename").notNull().unique(), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), }); ``` 3. Add two columns to the `documents` table: ```typescript formTemplateId: text("form_template_id").references(() => formTemplates.id), // nullable: custom uploads have no template filePath: text("file_path"), // relative path within uploads/ e.g. "clients/{clientId}/{uuid}.pdf"; nullable for legacy rows ``` NOTE: Use `text` for IDs (not `integer`) — the existing schema uses `text` PKs with crypto.randomUUID() throughout. The formTemplates.id is also text. Keep consistent. Create `teressa-copeland-homes/seeds/forms/.gitkeep` (empty file) so the directory is tracked. Then run the migration: ```bash cd teressa-copeland-homes && npx drizzle-kit generate && npx drizzle-kit migrate ``` Confirm migration applied without errors. ```bash cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx drizzle-kit migrate 2>&1 | tail -5 ``` Expected: "No migrations to run" or "Applied N migrations" (no errors). form_templates table and new documents columns exist in the database. seeds/forms/ directory tracked in git. Task 2: Create seed script and wire npm run seed:forms teressa-copeland-homes/scripts/seed-forms.ts teressa-copeland-homes/package.json Create `teressa-copeland-homes/scripts/seed-forms.ts`: ```typescript import 'dotenv/config'; import { readdir } from 'node:fs/promises'; import path from 'node:path'; import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { formTemplates } from '@/lib/db/schema'; const SEEDS_DIR = path.join(process.cwd(), 'seeds', 'forms'); async function seedForms() { const files = await readdir(SEEDS_DIR); const pdfs = files.filter(f => f.endsWith('.pdf')); if (pdfs.length === 0) { console.log('No PDF files found in seeds/forms/. Add PDFs downloaded from the SkySlope portal and re-run.'); return; } let seeded = 0; for (const filename of pdfs) { const name = filename .replace(/\.pdf$/i, '') .replace(/[-_]/g, ' ') .replace(/\b\w/g, c => c.toUpperCase()); await db.insert(formTemplates) .values({ name, filename }) .onConflictDoUpdate({ target: formTemplates.filename, set: { name, updatedAt: new Date() }, }); seeded++; } console.log(`Seeded ${seeded} forms into form_templates.`); process.exit(0); } seedForms().catch(err => { console.error(err); process.exit(1); }); ``` NOTE: The project uses `.env.local` not `.env`. Check how the existing seed-clients.ts (Phase 3) handles env loading — mirror that pattern exactly (likely `DOTENV_CONFIG_PATH=.env.local` prefix per the STATE.md decision). If the project uses `dotenv/config` with that env var, replace the top import accordingly. Add to `teressa-copeland-homes/package.json` scripts: ```json "seed:forms": "DOTENV_CONFIG_PATH=.env.local npx tsx scripts/seed-forms.ts" ``` Verify it runs cleanly on an empty seeds/forms/ dir (prints the "No PDF files" message and exits 0). ```bash cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run seed:forms 2>&1 ``` Expected: "No PDF files found in seeds/forms/" message (or seeds count if PDFs present). No unhandled errors. npm run seed:forms runs without error. Monthly sync workflow: agent downloads PDFs to seeds/forms/, re-runs npm run seed:forms. - `form_templates` table in schema.ts with id (text PK), name, filename (unique), createdAt, updatedAt - `documents` table has `formTemplateId` (text, nullable) and `filePath` (text, nullable) columns - Migration applied: `npx drizzle-kit migrate` reports no pending migrations - `seeds/forms/.gitkeep` exists - `npm run seed:forms` exits 0 (empty dir case: prints guidance message) Schema changes applied to the local PostgreSQL database. Seed script runnable. Monthly sync mechanism documented and working (re-run after adding PDFs to seeds/forms/). After completion, create `.planning/phases/04-pdf-ingest/04-01-SUMMARY.md`