PRD · Phase 1 · Subjects + Exports core

Phase 1 PRD: Subjects + Exports core draft

First production phase of the Pics Portal feature build. Establishes the subject as the load-bearing entity, ships the four Subjects-tab workflows from the PlicGo / Captura incumbent (grid, new, find duplicates, directory), and delivers the 25 standard SIS exports as async jobs with downloadable history.

Reading order

Skim the vision first if you haven't. The PlicGo feature inventory is the UX reference. The Pics API gap frames the open questions at the bottom.

Summary

Studios can, for any event:

  1. Import or hand-enter a subject roster.
  2. Browse, filter, search, and edit subjects in a grid view.
  3. Run duplicate detection across user-chosen fields and resolve the matches.
  4. Download a grouped subject directory PDF.
  5. Run any of 25 standard SIS exports as an async job and download the result.

End-to-end, no engineering hand-holding required, scoped to a single event.

Out of scope for Phase 1

Personas in scope

Phase 1 is staffed by Pics admins and studio operators. Both are authenticated through the existing Pics admin login. Customer roles are out of scope here — see vision doc personas for the full set.

User stories

Subject management

  1. As a studio operator, I can navigate from an event detail page to its subject roster.
  2. As a studio operator, I can see all subjects in the event in a paginated card grid with filters (Teacher, Grade, Images present/missing), a sort dropdown, and a free-text search.
  3. As a studio operator, I can add a single subject by filling in a form.
  4. As a studio operator, I can edit a subject by clicking into its card.
  5. As a studio operator, I can delete a subject with a confirmation step.
  6. As a studio operator, I can import a CSV roster and see staged subjects appear in the grid.

Duplicates

  1. As a studio operator, I can open Find Duplicates, pick the fields to match on (defaults: First Name, Last Name, Student ID), and see grouped match results.
  2. As a studio operator, I can mark a match as a real duplicate (and merge / keep one) or as a false positive (and dismiss).

Directory

  1. As a studio operator, I can request a Subject Directory PDF, pick a Group By field, and download the PDF when it's ready.

Exports

  1. As a studio operator, I can pick from 25 standard SIS export formats, kick off an export job, and watch it complete.
  2. As a studio operator, I can see a history of past exports with format, subject count, creator, timestamp, and status.
  3. As a studio operator, I can redownload a completed export.
  4. As a studio operator, I can cancel a queued export job and delete a completed export from the history.

Functional requirements

Subject model

A subject is a person being photographed at an event. Each subject belongs to exactly one event. The schema has a stable core plus an extensible custom-field surface.

Core fields

Extensible fields

An additional custom_fields column (JSON or JSONB depending on storage substrate) carries the long-tail PlicGo schema: Sequence Number, Record Number, Yearbook Pic, Custom 5, Custom 9, Home Phone, Address, City, State, Zip, Library ID, Cafeteria ID, Prefix, Position, Period, Track, Home Room, Shoot #, Code, Proof #, Packages, Date of Birth. Each is a string. The portal's import / form / export pipelines treat custom_fields keys as first-class for purposes of column mapping in exports.

Schema location is an open question

Phase 1 PRD does not commit to where this table lives (portal Postgres vs. Pics-owned via new API endpoints vs. hybrid). The schema is described in storage-abstract terms; the subject-store ADR picks the substrate. See Pics API gap for the three postures.

Subjects grid

Per-event subject roster, accessed from the event detail page via a Subjects sub-tab (next to the existing event metadata + CSV-staging card).

New Subject form

Edit Subject

Bulk import via CSV

Generalizes the existing event-level CSV-staging flow into a subject importer.

Find Duplicates

Download Subject Directory

Standard SIS exports — 25 formats

Each format is a deterministic transformation over the subject roster + the (Phase 2-populated) image set. In Phase 1 we ship the 25 formats listed in the PlicGo feature inventory:

Admin Plus, Alexandria, Aries by Eagle, Aspen VA SIS, Destiny by Follett, EduPoint Synergy, Fastlane 2000, Fast Track, Genesis, Infinite Campus, JMC, Lunchbox, Meals Plus, Mealtracker, Name Images with Student ID, Name Images with Student's Name, Nutri-Kids, PowerSchool, PSPA Yearbook (LEGACY), SASI XP, Skyward, SMS, SPOA School Software Universal Export, Star Base, WinSnap.

Each format is defined by a structured descriptor (column mapping × image naming × folder grouping × file packaging). The descriptors live in version-controlled code in Phase 1; Phase 3 lifts them into a database-backed authoring surface. Until then, we maintain them as TypeScript modules under lib/exports/formats/.

Per-format data:

Format fidelity

Studios switching from PlicGo / Captura need byte-comparable exports for their downstream SIS systems. Each format gets a golden-file test fixture: a known subject set in, a known archive out. Test suite verifies fixture-out equality (modulo a sanitized timestamp).

Export jobs + download history

Job substrate (shared across exports, directory PDFs, large imports)

The job substrate is an internal contract:

The substrate implementation (Supabase queue, Vercel background functions, dedicated service, etc.) is an ADR. The PRD only commits to the interface.

Information architecture

Route additions on top of the current scaffold:

/events/[eventId]                                       (existing) event detail + CSV staging
/events/[eventId]/subjects                              grid
/events/[eventId]/subjects/new                          new subject form
/events/[eventId]/subjects/[subjectId]                  edit subject
/events/[eventId]/subjects/import                       CSV import modal route (modal-overlay style)
/events/[eventId]/subjects/duplicates                   find duplicates
/events/[eventId]/subjects/directory                    directory request
/events/[eventId]/exports                               exports landing (tabs)
/events/[eventId]/exports/[jobId]                       export job detail / progress
/events/[eventId]/exports/directory/[jobId]             directory job detail / progress

Non-functional requirements

Scale

Accessibility

Observability

Security

Telemetry / success metrics

UX references

Phase 1's surfaces map 1:1 to the PlicGo / Captura screenshots:

Open questions

  1. Subject store — portal Postgres (Supabase), Pics expansion, or hybrid? → dedicated ADR (next, immediately after this PRD review). Load-bearing for Phase 1 implementation.
  2. Async job substrate — Supabase queue/cron, Vercel Background Functions, or dedicated queue service (BullMQ on Redis, Trigger.dev, etc.)? → ADR before Phase 1 implementation. Affects job rows, worker placement, cost model.
  3. Image source for "Named Images" exports — Pics image endpoint (which doesn't exist yet), portal-cached copy, or skip in Phase 1 and defer all image-bearing exports to Phase 2? → resolve before scoping; current preference is "defer image-bearing exports to Phase 2" so Phase 1 ships data-only exports cleanly.
  4. Duplicate-match algorithm — exact-match only (matches PlicGo and proposed default), or add a fuzzy / phonetic toggle? → resolve in this PRD; current proposal is exact-only with fuzzy deferred.
  5. Custom-fields shape — JSONB blob vs. a structured custom-fields table with per-studio field definitions vs. PlicGo's fixed custom_5/custom_9 positional columns? → resolve in this PRD; current proposal is JSONB blob with per-studio field-definition metadata (gets us flexibility without per-studio schema migrations).
  6. Subject identity vs. event scope — does the same real person across two events get one subject row or two? Affects de-duplication across the studio. → resolve in this PRD; current proposal is "one subject per event" (matches PlicGo), with cross-event identity deferred until we have a real use case for it.
  7. CSV import header inference — strict exact-match on header names, or fuzzy match with a confirmation step? → resolve in this PRD; current proposal is exact-match with a confirmation step where the user maps unmatched columns manually.
  8. Soft-delete TTL — how long do deleted / merged subjects persist before hard delete? → propose 90 days; revisit in security review.
  9. Standard formats: which 25 ship — the inventory captured 25 names but format fidelity vs. PlicGo's actual byte output is unknown. Do we need access to PlicGo sample outputs as golden fixtures, or do we work from SIS documentation? → operational question; flag to product before impl planning.

Dependencies

What lands after this PRD is approved

  1. ADR pack — at least: subject-store, async job substrate. Plus image strategy if we change posture on open question 3.
  2. Phase 1 implementation plan with verifiable steps.
  3. Linear epic + tickets seeded from the impl plan.