Files
red/.planning/phases/04-pdf-ingest/04-01-PLAN.md
Chandler Copeland c896fa5e82 docs(04-pdf-ingest): create phase 4 plan (4 plans, 4 waves)
Plans 04-01 through 04-04 cover DOC-01, DOC-02, DOC-03:
schema/seed, API routes, UI modal + PDF viewer, human verification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:28:39 -06:00

8.4 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
04-pdf-ingest 01 execute 1
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
true
DOC-01
DOC-02
truths artifacts key_links
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)
path provides contains
teressa-copeland-homes/src/lib/db/schema.ts formTemplates table definition and updated documents table formTemplates
path provides exports
teressa-copeland-homes/scripts/seed-forms.ts CLI seed script for populating form_templates from seeds/forms/
path provides
teressa-copeland-homes/seeds/forms/.gitkeep Tracked placeholder for manually-downloaded PDF seed files
from to via pattern
scripts/seed-forms.ts seeds/forms/ node:fs/promises readdir readdir.*seeds/forms
from to via pattern
scripts/seed-forms.ts src/lib/db/schema.ts formTemplates drizzle insert onConflictDoUpdate 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.

<execution_context> @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md </execution_context>

@.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(), });

</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add formTemplates table and extend documents table in schema.ts</name>
  <files>
    teressa-copeland-homes/src/lib/db/schema.ts
    teressa-copeland-homes/seeds/forms/.gitkeep
  </files>
  <action>
    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.
  </action>
  <verify>
    ```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).
  </verify>
  <done>form_templates table and new documents columns exist in the database. seeds/forms/ directory tracked in git.</done>
</task>

<task type="auto">
  <name>Task 2: Create seed script and wire npm run seed:forms</name>
  <files>
    teressa-copeland-homes/scripts/seed-forms.ts
    teressa-copeland-homes/package.json
  </files>
  <action>
    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).
  </action>
  <verify>
    ```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.
  </verify>
  <done>npm run seed:forms runs without error. Monthly sync workflow: agent downloads PDFs to seeds/forms/, re-runs npm run seed:forms.</done>
</task>

</tasks>

<verification>
- `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)
</verification>

<success_criteria>
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/).
</success_criteria>

<output>
After completion, create `.planning/phases/04-pdf-ingest/04-01-SUMMARY.md`
</output>