Architecture
Data Model

Data Model

Core entities and their relationships. PostgreSQL via Drizzle ORM.


Entity Relationship

User (1) ──── (many) Collection
                       β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚        β”‚        β”‚
        Invitation  Response  Synthesis
         (many)     (many)    (many versions)
              β”‚        β”‚
              β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜
                   β”‚
            Response links to
            Invitation (nullable)

Entities

User

The authenticated creator. Links to Clerk via auth_provider_id.

ColumnTypeNotes
idUUIDPrimary key
auth_provider_idstringClerk user ID
statusenumactive Β· unverified Β· suspended Β· deleted
emailstringUnique
display_namestring
avatar_urlstring?From social login or custom upload
company_namestring?For report branding
company_logo_urlstring?GCS URL
timezonestringIANA timezone, auto-detected at signup
notification_preferencesJSONEmail notification toggles
planenumfree Β· starter Β· professional Β· enterprise
stripe_customer_idstring?
created_attimestamp
updated_attimestamp
deleted_attimestamp?Soft delete, 30-day recovery window

Collection

Top-level entity for one knowledge extraction exercise.

ColumnTypeNotes
idUUIDPrimary key
creator_idUUIDFK β†’ User
statusenumdraft Β· collecting Β· synthesising Β· complete Β· closed
titlestring
purposetext"What are you trying to find out?"
motivationtext"Why does this matter?"
audience_descriptiontext"Who are you asking?"
report_audiencetext"Who will read this report?"
distribution_modeenumopen_link Β· invite Β· both
deadlinetimestamp?
min_responsesintDefault: 3
auto_synthesiseboolean
respondent_thank_youtext?Custom completion message
anonymousbooleanFor open link mode
response_capint?Max open link responses
password_hashstring?Optional questionnaire password
created_attimestamp
updated_attimestamp
closed_attimestamp?

Invitation

Individual email invitations with delivery and completion tracking.

ColumnTypeNotes
idUUIDPrimary key
collection_idUUIDFK β†’ Collection
emailstring
namestring?
rolestring?e.g. "peer", "manager", "direct report"
unique_tokenstringURL-safe, used in invite link
statusenumpending Β· sent Β· opened Β· started Β· completed
sent_attimestamp?
opened_attimestamp?
reminded_attimestamp?
completed_attimestamp?

Response

A completed questionnaire submission.

ColumnTypeNotes
idUUIDPrimary key
collection_idUUIDFK β†’ Collection
invitation_idUUID?FK β†’ Invitation (null for open link)
respondent_namestring?
respondent_emailstring?
sourceenumopen_link Β· invite
rolestring?
form_dataJSONThe actual answers
statusenumin_progress Β· completed
started_attimestamp
completed_attimestamp?

Synthesis

AI-generated report. Versioned β€” a collection may have multiple.

ColumnTypeNotes
idUUIDPrimary key
collection_idUUIDFK β†’ Collection
versionintIncremented on regeneration
contentJSONStructured AI output
rendered_htmltext
rendered_pdf_urlstring?GCS URL
prompt_snapshottextFull prompt sent to Claude (for debugging)
focus_instructionstext?Creator guidance for regeneration
response_countintResponses included in this synthesis
generated_attimestamp

Design Notes

Organisation (Post-MVP)

The User table should include a nullable organisation_id FK from day one, even though the Organisation entity won't be built until post-MVP. This avoids a migration headache later.

Respondent Identity Linking

If a respondent later signs up as a creator with the same email, their previous identified responses are associated with the new account. Anonymous responses are never retroactively linked. Anonymity guarantees are sacrosanct.

Soft Deletes

User deletion is a soft delete with a 30-day recovery window. After 30 days, cascading hard delete removes all collections, responses, invitations, and syntheses. Financial records required by law are retained separately.