Database (Core)
The five tables centred on projects and candidates.
users
Overview
A console user, one-to-one with a Google account. Interviewers are rows in this table too.
Table Definition
| Logical name | Physical name | Column | Type | PK | Relation | Unique | Nullable | Default | Notes |
|---|---|---|---|---|---|---|---|---|---|
| User | users |
id |
integer | โ | auto-increment | ||||
google_id |
varchar(255) | โ | Unique ID of the Google account | ||||||
email |
varchar(255) | โ | |||||||
name |
varchar(255) | Display name | |||||||
avatar_url |
varchar(500) | โ | Google profile image | ||||||
role |
user_role | 'member' |
admin / member / viewer |
||||||
created_at |
timestamp | now() |
|||||||
updated_at |
timestamp | now() |
Indexes
- Primary key (
id) - UNIQUE:
google_id - UNIQUE:
email - INDEX:
idx_users_email(email)
Notes
- Users are created automatically during the Google OAuth callback (registered on first login)
roledefaults tomember; demotion tovieweror promotion toadminis currently done directly in the DBreservations.interviewer_idreferences this table withonDelete: restrict, so a user with outstanding reservations cannot be deleted
projects
Overview
One interview initiative. Holds the interview conditions, scoring rules, survey import settings, and the entire arrange flow state.
Table Definition
| Logical name | Physical name | Column | Type | PK | Relation | Unique | Nullable | Default | Notes |
|---|---|---|---|---|---|---|---|---|---|
| Project | projects |
id |
integer | โ | auto-increment | ||||
name |
varchar(255) | Project name | |||||||
description |
text | โ | |||||||
status |
project_status | 'draft' |
draft / active / completed / archived |
||||||
interview_duration_minutes |
integer | 60 |
Duration of one interview in minutes | ||||||
scheduling_range_start_days |
integer | 1 |
Start of the scheduling window (N days from today) | ||||||
scheduling_range_end_days |
integer | 14 |
End of the scheduling window (N days from today) | ||||||
scoring_rules |
json | โ | ScoringRules type |
||||||
interview_type |
interview_type | 'any' |
online_meet / online_zoom / offline / any |
||||||
google_calendar_id |
varchar(255) | โ | Project-specific calendar | ||||||
survey_auto_import_enabled |
boolean | false |
Enables automatic import | ||||||
survey_id |
varchar(50) | โ | Linked survey ID (logical reference to databricks_surveys.survey_id) |
||||||
survey_import_mapping |
json | โ | ImportMapping type |
||||||
survey_import_filter |
json | โ | ImportFilter type |
||||||
survey_last_imported_at |
timestamp | โ | Last import timestamp | ||||||
arrange_settings |
json | โ | ArrangeSettings type (STEP1โ3) |
||||||
created_by |
integer | users:id (logical) | Creator | ||||||
created_at |
timestamp | now() |
|||||||
updated_at |
timestamp | now() |
Relations
created_byโusers.id: declared only in Drizzle'srelations(); there is no database-level foreign key constraint- Child tables (
onDelete: cascade):project_candidates/project_interviewers/email_templates/import_logs
Indexes
- Primary key (
id) - INDEX:
idx_projects_status(status) - INDEX:
idx_projects_created_by(created_by)
Notes
arrange_settingsstores the whole arrange flow state in a single JSON column by design; there is no per-step table- When
scoring_rules.max_scoreis set, it caps the computed score - The scheduling window is stored as days relative to today, not as absolute dates
candidates
Overview
The candidate as a person. Unique across projects โ one row even if the same person joins several projects.
Table Definition
| Logical name | Physical name | Column | Type | PK | Relation | Unique | Nullable | Default | Notes |
|---|---|---|---|---|---|---|---|---|---|
| Candidate | candidates |
id |
integer | โ | auto-increment | ||||
external_user_id |
varchar(255) | โ | Respondent ID from the external survey platform | ||||||
email |
varchar(255) | Personal data | |||||||
phone |
varchar(50) | โ | Personal data | ||||||
name |
varchar(255) | Personal data | |||||||
created_at |
timestamp | now() |
|||||||
updated_at |
timestamp | now() |
Indexes
- Primary key (
id) - UNIQUE:
external_user_id - INDEX:
idx_candidates_email(email)
Notes
emailis not unique โ different respondents may share an address, soexternal_user_idis the uniqueness keyemail/phone/nameare personal data and are anonymized by thepii-cleanupbatch once past the retention period (3 months by default)- Related rows such as
project_candidatessurvive anonymization for aggregation
project_candidates
Overview
A candidate's participation in a project. Holds all project-scoped information โ status, score, selection type, answers โ and is the central table of the system.
Table Definition
| Logical name | Physical name | Column | Type | PK | Relation | Unique | Nullable | Default | Notes |
|---|---|---|---|---|---|---|---|---|---|
| Project candidate | project_candidates |
id |
integer | โ | auto-increment | ||||
project_id |
integer | projects:id (cascade) | |||||||
candidate_id |
integer | candidates:id (cascade) | |||||||
attributes |
jsonb | โ | Attributes (prefecture, age, etc.) | ||||||
survey_responses |
jsonb | โ | Survey answers (pivoted) | ||||||
score |
decimal(10,2) | โ | Scoring result | ||||||
selection_type |
selection_type | โ | primary / reserve |
||||||
segment_id |
varchar(255) | โ | Logical key into arrange_settings.step2.segments[].id |
||||||
memo |
text | โ | Free-form memo | ||||||
priority |
varchar(1) | โ | Priority (A / B / C) |
||||||
status |
pc_status | 'not_contacted' |
10-value enum | ||||||
created_at |
timestamp | now() |
|||||||
updated_at |
timestamp | now() |
Relations
project_idโprojects.id(onDelete: cascade)candidate_idโcandidates.id(onDelete: cascade)- Child tables (onDelete: cascade):
email_logs/scheduling_tokens/candidate_available_dates/reservations/status_histories
Indexes
- Primary key (
id) - UNIQUE INDEX:
uk_project_candidate(project_id,candidate_id) - INDEX:
idx_pc_project_status(project_id,status) - INDEX:
idx_pc_project_score(project_id,score)
Notes
uk_project_candidatemakes duplicate enrolment in one project impossiblestatusmay only move along the transitions inVALID_STATUS_TRANSITIONS(see the diagram in Features); invalid moves raiseInvalidStatusTransitionErrorsegment_idis not a foreign key โ it is a string pointing at a segment definition insideprojects.arrange_settingsattributesandsurvey_responsesare bothjsonb; the candidate list'sresponseFilterquery filters across the two
project_interviewers
Overview
Join table between projects and interviewers (users).
Table Definition
| Logical name | Physical name | Column | Type | PK | Relation | Unique | Nullable | Default | Notes |
|---|---|---|---|---|---|---|---|---|---|
| Project interviewer | project_interviewers |
id |
integer | โ | auto-increment | ||||
project_id |
integer | projects:id (cascade) | |||||||
user_id |
integer | users:id (cascade) | |||||||
created_at |
timestamp | now() |
Relations
project_idโprojects.id(onDelete: cascade)user_idโusers.id(onDelete: cascade)
Indexes
- Primary key (
id) - UNIQUE INDEX:
uk_project_user(project_id,user_id)
Notes
- The main / sub distinction is not stored here but in
projects.arrange_settings.step1.mainInterviewerIds/subInterviewerIds - Availability calculation reads the Google Calendars of the interviewers registered here