Applicant Tracking System — Semantic Model
1. Overview
An applicant tracking system used by an in-house recruiting team to manage open job requisitions, the candidates considered for them, and the full hiring funnel from application through interviews to offer. Primary users are recruiters, hiring managers, and interviewers; the system records who applied for what, where they are in the pipeline, what feedback interviewers gave, and what offers were extended and accepted.
2. Entity summary
| # | Table name | Singular label | Purpose |
|---|---|---|---|
| 1 | departments | Department | Organizational units that own job openings (e.g. Engineering, Sales). Supports an optional parent–child hierarchy. |
| 2 | job_openings | Job Opening | A specific role being hired for, with status, hiring team, headcount, and target start date. |
| 3 | application_stages | Application Stage | Configurable pipeline steps (e.g. New, Phone Screen, On-site, Offer, Hired, Rejected) with order and category. |
| 4 | candidate_sources | Candidate Source | Where candidates come from (job board, referral, agency, inbound, sourced). |
| 5 | candidates | Candidate | A person in the talent pool. Exists independently of any specific job application. |
| 6 | job_applications | Job Application | A candidate applying to a specific job opening — the central pipeline record. |
| 7 | candidate_documents | Candidate Document | Resumes, cover letters, portfolios, work samples attached to a candidate. |
| 8 | application_notes | Application Note | Comment thread on an application — recruiter and hiring-manager observations. |
| 9 | interviews | Interview | A scheduled interview event tied to an application (kind, time, location/URL). |
| 10 | interview_feedback | Interview Feedback | Scorecard from one interviewer for one interview — rating, recommendation, notes. |
| 11 | offers | Offer | A formal offer extended to a candidate for a job — terms, status, candidate response. |
| 12 | hiring_team_members | Hiring Team Member | Junction: a user assigned to a job opening with a role (recruiter, hiring manager, interviewer, coordinator). |
| 13 | users | User | System users (recruiters, hiring managers, interviewers, coordinators). Modeled for self-containment; deployer dedupes against the Semantius built-in. |
Entity-relationship diagram
flowchart LR
departments -->|has children| departments
users -->|heads| departments
departments -->|employs| users
departments -->|owns| job_openings
users -->|manages| job_openings
users -->|recruits for| job_openings
candidate_sources -->|sources| candidates
users -->|referred| candidates
candidates -->|applies via| job_applications
job_openings -->|receives| job_applications
application_stages -->|stages| job_applications
candidate_sources -->|sources| job_applications
users -->|assigned to| job_applications
candidates -->|owns| candidate_documents
users -->|uploaded| candidate_documents
job_applications -->|has| application_notes
users -->|authored| application_notes
job_applications -->|has| interviews
users -->|coordinates| interviews
interviews -->|has| interview_feedback
users -->|gives| interview_feedback
job_applications -->|has| offers
users -->|approves| offers
job_openings -->|staffed by| hiring_team_members
users -->|on team| hiring_team_members
3. Entities
3.1 departments — Department
Plural label: Departments
Label column: department_name
Audit log: no
Description: An organizational unit that owns one or more job openings. Supports an optional parent–child hierarchy so business units can contain sub-teams.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
department_name | string | yes | Department Name | label_column; unique |
department_code | string | no | Code | unique (e.g. ENG, SALES) |
parent_department_id | reference | no | Parent Department | → departments (N:1), self-reference for hierarchy |
head_user_id | reference | no | Department Head | → users (N:1) |
Relationships
- A
departmentmay have aparent_departmentand many childdepartments(1:N self-reference, optional). - A
departmentmay be headed by oneuser(N:1, optional). - A
departmentowns manyjob_openings(1:N, viajob_openings.department_id). - Many
usersmay belong to adepartment(1:N, viausers.department_id).
3.2 job_openings — Job Opening
Plural label: Job Openings
Label column: job_title
Audit log: yes (status changes and salary band updates are subject to dispute; keep a history)
Description: A specific position the company is hiring for. Created in draft, opened for applications, then transitions through on_hold / filled / closed / cancelled. Has one hiring manager and an optional lead recruiter; additional team members are tracked via hiring_team_members.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
job_title | string | yes | Job Title | label_column |
job_code | string | no | Requisition Code | unique (e.g. ENG-2026-014) |
department_id | reference | yes | Department | → departments (N:1, restrict) |
hiring_manager_id | reference | yes | Hiring Manager | → users (N:1, restrict) |
recruiter_id | reference | no | Lead Recruiter | → users (N:1, clear) |
employment_type | enum | yes | Employment Type | values: full_time, part_time, contract, internship, temporary |
work_arrangement | enum | yes | Work Arrangement | values: onsite, remote, hybrid |
location | string | no | Location | |
status | enum | yes | Status | values: draft, open, on_hold, filled, closed, cancelled |
headcount | integer | yes | Headcount | how many to hire |
opened_at | date | no | Opened | |
target_start_date | date | no | Target Start Date | |
filled_at | date | no | Filled | |
salary_min | float | no | Salary Min | |
salary_max | float | no | Salary Max | |
salary_currency | string | no | Currency | ISO 4217 code (e.g. USD) |
job_description | html | no | Description | rich-text role description |
job_requirements | text | no | Requirements | required experience, skills, etc. |
Relationships
- A
job_openingbelongs to onedepartment(N:1, required, restrict). - A
job_openinghas onehiring_manageruser (N:1, required, restrict) and optionally one leadrecruiteruser (N:1, optional, clear). - A
job_openingreceives manyjob_applications(1:N, viajob_applications.job_opening_id, restrict — applications must be archived before a job can be deleted). - A
job_openinghas manyhiring_team_members(1:N, via thehiring_team_membersjunction).
3.3 application_stages — Application Stage
Plural label: Application Stages
Label column: stage_name
Audit log: no
Description: A configurable step in the application pipeline. Stages are shared across all job openings (single global pipeline assumption — see §6.1). stage_order controls sort; stage_category groups stages so reports and downstream logic can reason about pipeline phase without parsing names.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
stage_name | string | yes | Stage Name | label_column; unique |
stage_order | integer | yes | Order | sort order in the pipeline |
stage_category | enum | yes | Category | values: pre_screen, screening, interview, offer, hired, rejected |
is_active | boolean | yes | Active | default true |
description | text | no | Description |
Relationships
- An
application_stagemay be the current stage of manyjob_applications(1:N, viajob_applications.current_stage_id, restrict — stages cannot be deleted while in use).
3.4 candidate_sources — Candidate Source
Plural label: Candidate Sources
Label column: source_name
Audit log: no
Description: A named source from which candidates and applications originate, used for sourcing analytics. Examples: “LinkedIn Jobs”, “Employee Referral Program”, “Acme Recruiting Agency”.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
source_name | string | yes | Source Name | label_column; unique |
source_type | enum | yes | Source Type | values: job_board, referral, agency, inbound, sourced, social_media, career_site, event, other |
is_active | boolean | yes | Active | default true |
description | text | no | Description |
Relationships
- A
candidate_sourcemay be the source of manycandidates(1:N, viacandidates.source_id, clear). - A
candidate_sourcemay be the source of manyjob_applications(1:N, viajob_applications.source_id, clear) — the application can be tagged with a different source than the candidate (e.g. candidate originally sourced from LinkedIn, but applied via referral for this specific role).
3.5 candidates — Candidate
Plural label: Candidates
Label column: full_name
Audit log: yes (personal data subject to GDPR / data-subject access requests; preserve a change history)
Description: A person in the talent pool. A candidate exists independently of any specific job and may have multiple job_applications over time. Identity is loosely keyed on email_address (unique when present) — duplicates are detected at the recruiter’s discretion.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
full_name | string | yes | Full Name | label_column |
first_name | string | no | First Name | |
last_name | string | no | Last Name | |
email_address | email | no | unique | |
phone_number | string | no | Phone | |
linkedin_url | url | no | ||
current_employer | string | no | Current Employer | |
current_job_title | string | no | Current Job Title | |
location_city | string | no | City | |
location_country | string | no | Country | |
source_id | reference | no | Source | → candidate_sources (N:1, clear) |
referrer_user_id | reference | no | Referred By | → users (N:1, clear) — the employee who made the referral, when source is a referral |
candidate_status | enum | yes | Candidate Status | values: active, hired, archived, do_not_contact |
notes | text | no | Notes | candidate-level notes (vs application-level) |
Relationships
- A
candidatemay originate from onecandidate_source(N:1, optional, clear). - A
candidatemay be referred by oneuser(N:1, optional, clear). - A
candidateowns manyjob_applications(1:N, viajob_applications.candidate_id, cascade — deleting a candidate wipes their applications, supporting GDPR erasure). - A
candidateowns manycandidate_documents(1:N, viacandidate_documents.candidate_id, cascade).
3.6 job_applications — Job Application
Plural label: Job Applications
Label column: application_label
Audit log: yes (stage transitions and status changes are central to the audit trail of a hiring decision)
Description: The central pipeline record — a specific candidate applying to a specific job opening, currently sitting at one application stage. Created when a candidate applies (or when a recruiter pulls them into a role) and progresses through stages until terminal status (hired, rejected, withdrawn).
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
application_label | string | yes | Application | label_column; caller composes on insert (e.g. "{candidate.full_name} → {job_opening.job_title}") |
candidate_id | parent | yes | Candidate | ↳ candidates (N:1, cascade) |
job_opening_id | reference | yes | Job Opening | → job_openings (N:1, restrict) — preserves history if a job is closed |
current_stage_id | reference | yes | Current Stage | → application_stages (N:1, restrict) |
status | enum | yes | Status | values: active, hired, rejected, withdrawn, on_hold |
source_id | reference | no | Source | → candidate_sources (N:1, clear) |
applied_at | date-time | yes | Applied At | |
assigned_recruiter_id | reference | no | Assigned Recruiter | → users (N:1, clear) |
rejection_reason | enum | no | Rejection Reason | values: not_qualified, withdrew, position_filled, no_show, salary_mismatch, location_mismatch, culture_fit, other |
rejected_at | date-time | no | Rejected At | |
hired_at | date-time | no | Hired At |
Relationships
- A
job_applicationbelongs to onecandidateas its parent (N:1, required, cascade — owning lifecycle). - A
job_applicationreferences onejob_opening(N:1, required, restrict — historical applications survive a job closure). - A
job_applicationis currently at oneapplication_stage(N:1, required, restrict). - A
job_applicationmay originate from onecandidate_source(N:1, optional, clear). - A
job_applicationmay be assigned to oneuserrecruiter (N:1, optional, clear). - A
job_applicationhas manyapplication_notes,interviews, andoffers(1:N, all cascade exceptofferswhich isrestrict— see §3.11).
3.7 candidate_documents — Candidate Document
Plural label: Candidate Documents
Label column: document_label
Audit log: no
Description: A document attached to a candidate — resume, cover letter, portfolio, work sample, certification, or reference letter. Stored as a URL pointing to file storage; the model does not own the binary itself.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
document_label | string | yes | Document | label_column; caller composes (e.g. "Resume — Jane Doe") |
candidate_id | parent | yes | Candidate | ↳ candidates (N:1, cascade) |
document_type | enum | yes | Document Type | values: resume, cover_letter, portfolio, work_sample, certification, reference_letter, other |
file_url | url | yes | File URL | external storage URL |
file_name | string | no | File Name | original uploaded filename |
uploaded_at | date-time | yes | Uploaded At | |
uploaded_by_user_id | reference | no | Uploaded By | → users (N:1, clear) |
Relationships
- A
candidate_documentbelongs to onecandidateas its parent (N:1, required, cascade — documents are wiped if the candidate is erased). - A
candidate_documentmay be uploaded by oneuser(N:1, optional, clear).
3.8 application_notes — Application Note
Plural label: Application Notes
Label column: note_subject
Audit log: no
Description: A note left on an application — a recruiter or hiring-manager observation, decision rationale, or coordination message. Visibility controls who can read the note (whole hiring team vs. recruiters only vs. publicly visible to the candidate).
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
note_subject | string | yes | Subject | label_column; short summary line |
application_id | parent | yes | Application | ↳ job_applications (N:1, cascade) |
author_user_id | reference | yes | Author | → users (N:1, restrict) — preserves authorship audit trail |
note_body | text | yes | Note | |
visibility | enum | yes | Visibility | values: hiring_team, recruiter_only, public |
noted_at | date-time | yes | Noted At |
Relationships
- An
application_notebelongs to onejob_applicationas its parent (N:1, required, cascade). - An
application_noteis authored by oneuser(N:1, required, restrict — author cannot be deleted while their notes exist).
3.9 interviews — Interview
Plural label: Interviews
Label column: interview_label
Audit log: no
Description: A scheduled interview event for a specific application. May involve one or more interviewers (each captured as their own interview_feedback row). Status transitions from scheduled through completed / cancelled / no_show / rescheduled.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
interview_label | string | yes | Interview | label_column; caller composes (e.g. "Tech Phone Screen — Jane Doe") |
application_id | parent | yes | Application | ↳ job_applications (N:1, cascade) |
interview_kind | enum | yes | Kind | values: phone_screen, video_call, onsite, technical, take_home, panel, final, reference_check |
scheduled_start | date-time | yes | Start | |
scheduled_end | date-time | yes | End | |
location | string | no | Location | physical location for onsite interviews |
meeting_url | url | no | Meeting URL | video-call link |
status | enum | yes | Status | values: scheduled, completed, cancelled, no_show, rescheduled |
coordinator_user_id | reference | no | Coordinator | → users (N:1, clear) |
Relationships
- An
interviewbelongs to onejob_applicationas its parent (N:1, required, cascade). - An
interviewmay be coordinated by oneuser(N:1, optional, clear). - An
interviewhas manyinterview_feedbackrows — one per interviewer (1:N, viainterview_feedback.interview_id, cascade).
3.10 interview_feedback — Interview Feedback
Plural label: Interview Feedback
Label column: feedback_label
Audit log: yes (scorecards are decision evidence; preserve change history)
Description: One interviewer’s scorecard for one interview. An interview can have multiple feedback rows when multiple people attended (e.g. a panel). is_submitted distinguishes a draft scorecard from a finalized one.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
feedback_label | string | yes | Feedback | label_column; caller composes (e.g. "Alex Kim — Tech Phone Screen for Jane Doe") |
interview_id | parent | yes | Interview | ↳ interviews (N:1, cascade) |
interviewer_user_id | reference | yes | Interviewer | → users (N:1, restrict) — preserves authorship |
overall_rating | enum | no | Overall Rating | values: strong_yes, yes, lean_yes, lean_no, no, strong_no |
recommendation | enum | no | Recommendation | values: advance, hold, reject |
strengths | text | no | Strengths | |
concerns | text | no | Concerns | |
detailed_notes | text | no | Detailed Notes | |
is_submitted | boolean | yes | Submitted | default false |
submitted_at | date-time | no | Submitted At | populated when is_submitted flips to true |
Relationships
- An
interview_feedbackbelongs to oneinterviewas its parent (N:1, required, cascade). - An
interview_feedbackis authored by oneuserinterviewer (N:1, required, restrict — the interviewer cannot be deleted while feedback exists).
3.11 offers — Offer
Plural label: Offers
Label column: offer_label
Audit log: yes (offers are commitments — preserve full change history of terms, status, and approvals)
Description: A formal offer extended to a candidate for a specific application. Goes through draft → pending_approval → approved → sent, then accepted / declined / rescinded / expired. An application typically has at most one active offer; the model uses restrict so an offer is never silently lost when an application is cleaned up.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
offer_label | string | yes | Offer | label_column; caller composes (e.g. "Offer — Jane Doe — Senior Engineer") |
application_id | reference | yes | Application | → job_applications (N:1, restrict) |
status | enum | yes | Status | values: draft, pending_approval, approved, sent, accepted, declined, rescinded, expired |
base_salary | float | yes | Base Salary | |
salary_currency | string | yes | Currency | ISO 4217 code |
bonus_target | float | no | Bonus Target | annual on-target bonus |
equity_amount | string | no | Equity | free-text (shares, RSU value, percentages vary) |
start_date | date | no | Start Date | proposed start date |
offer_extended_at | date-time | no | Extended At | timestamp the offer was sent to the candidate |
offer_expires_at | date-time | no | Expires At | |
candidate_response | enum | yes | Candidate Response | values: pending, accepted, declined, no_response |
responded_at | date-time | no | Responded At | |
approver_user_id | reference | no | Approver | → users (N:1, clear) |
Relationships
- An
offerreferences onejob_application(N:1, required, restrict — preserves the offer record even if cleanup of the application is attempted). - An
offermay have one approvinguser(N:1, optional, clear).
3.12 hiring_team_members — Hiring Team Member
Plural label: Hiring Team Members
Label column: team_member_label
Audit log: no
Description: Junction associating a user with a job_opening in a specific role (recruiter, hiring manager, interviewer, coordinator, executive sponsor). A user can sit on many openings; an opening can have many team members in the same or different roles. The hiring manager and lead recruiter on job_openings are summary FKs for the most-common case; this junction holds the full team and additional roles.
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
team_member_label | string | yes | Team Member | label_column; caller composes (e.g. "Alex Kim — Hiring Manager — Senior Engineer") |
job_opening_id | parent | yes | Job Opening | ↳ job_openings (N:1, cascade) |
user_id | parent | yes | User | ↳ users (N:1, cascade) |
team_role | enum | yes | Role | values: recruiter, hiring_manager, interviewer, coordinator, executive_sponsor |
assigned_at | date-time | yes | Assigned At | |
is_active | boolean | yes | Active | default true — set false to remove from team without deleting history |
Relationships
- A
hiring_team_memberbelongs to onejob_openingand oneuser, both as parents (cascade on either side). users↔job_openingsis many-to-many through this junction.
3.13 users — User
Plural label: Users
Label column: display_name
Audit log: no
Description: A system user — recruiter, hiring manager, interviewer, or coordinator. Modeled here for self-containment. Semantius ships a built-in users table; the deployer reuses the built-in and only adds any of the fields below that the built-in lacks. All other entities reference this table via reference_table: "users".
Fields
| Field name | Format | Required | Label | Reference / Notes |
|---|---|---|---|---|
display_name | string | yes | Display Name | label_column |
email_address | email | yes | unique | |
first_name | string | no | First Name | |
last_name | string | no | Last Name | |
job_title | string | no | Job Title | this user’s own job title at the company |
department_id | reference | no | Department | → departments (N:1, clear) |
is_active | boolean | yes | Active | default true |
Relationships
- A
usermay belong to onedepartment(N:1, optional, clear). - A
usermay head one or moredepartments(1:N, viadepartments.head_user_id). - A
usermay be the hiring manager, lead recruiter, assigned recruiter, coordinator, interviewer, author, uploader, or approver across many other entities — see §4 for the full edge list. users↔job_openingsis many-to-many throughhiring_team_members.
4. Relationship summary
| From | Field | To | Cardinality | Kind | Delete behavior |
|---|---|---|---|---|---|
departments | parent_department_id | departments | N:1 | reference | clear |
departments | head_user_id | users | N:1 | reference | clear |
users | department_id | departments | N:1 | reference | clear |
job_openings | department_id | departments | N:1 | reference | restrict |
job_openings | hiring_manager_id | users | N:1 | reference | restrict |
job_openings | recruiter_id | users | N:1 | reference | clear |
candidates | source_id | candidate_sources | N:1 | reference | clear |
candidates | referrer_user_id | users | N:1 | reference | clear |
job_applications | candidate_id | candidates | N:1 | parent | cascade |
job_applications | job_opening_id | job_openings | N:1 | reference | restrict |
job_applications | current_stage_id | application_stages | N:1 | reference | restrict |
job_applications | source_id | candidate_sources | N:1 | reference | clear |
job_applications | assigned_recruiter_id | users | N:1 | reference | clear |
candidate_documents | candidate_id | candidates | N:1 | parent | cascade |
candidate_documents | uploaded_by_user_id | users | N:1 | reference | clear |
application_notes | application_id | job_applications | N:1 | parent | cascade |
application_notes | author_user_id | users | N:1 | reference | restrict |
interviews | application_id | job_applications | N:1 | parent | cascade |
interviews | coordinator_user_id | users | N:1 | reference | clear |
interview_feedback | interview_id | interviews | N:1 | parent | cascade |
interview_feedback | interviewer_user_id | users | N:1 | reference | restrict |
offers | application_id | job_applications | N:1 | reference | restrict |
offers | approver_user_id | users | N:1 | reference | clear |
hiring_team_members | job_opening_id | job_openings | N:1 | parent (junction) | cascade |
hiring_team_members | user_id | users | N:1 | parent (junction) | cascade |
5. Enumerations
5.1 job_openings.employment_type
full_timepart_timecontractinternshiptemporary
5.2 job_openings.work_arrangement
onsiteremotehybrid
5.3 job_openings.status
draftopenon_holdfilledclosedcancelled
5.4 application_stages.stage_category
pre_screenscreeninginterviewofferhiredrejected
5.5 candidate_sources.source_type
job_boardreferralagencyinboundsourcedsocial_mediacareer_siteeventother
5.6 candidates.candidate_status
activehiredarchiveddo_not_contact
5.7 job_applications.status
activehiredrejectedwithdrawnon_hold
5.8 job_applications.rejection_reason
not_qualifiedwithdrewposition_filledno_showsalary_mismatchlocation_mismatchculture_fitother
5.9 candidate_documents.document_type
resumecover_letterportfoliowork_samplecertificationreference_letterother
5.10 application_notes.visibility
hiring_teamrecruiter_onlypublic
5.11 interviews.interview_kind
phone_screenvideo_callonsitetechnicaltake_homepanelfinalreference_check
5.12 interviews.status
scheduledcompletedcancelledno_showrescheduled
5.13 interview_feedback.overall_rating
strong_yesyeslean_yeslean_nonostrong_no
5.14 interview_feedback.recommendation
advanceholdreject
5.15 offers.status
draftpending_approvalapprovedsentaccepteddeclinedrescindedexpired
5.16 offers.candidate_response
pendingaccepteddeclinedno_response
5.17 hiring_team_members.team_role
recruiterhiring_managerinterviewercoordinatorexecutive_sponsor
6. Open questions
6.1 🔴 Decisions needed (blockers)
None.
6.2 🟡 Future considerations (deferred scope)
- Should
application_stagesbe per-job-opening rather than global, so different role types (engineering vs sales vs executive) can have different pipelines? Adding ajob_pipelineslink entity and apipeline_idonjob_openingswould model this cleanly. - Should candidates be linked to a structured
skillstaxonomy (M:N viacandidate_skills), or is the unstructurednotesfield sufficient for v1? - Should rejection reasons be promoted from an enum to a configurable lookup table (
rejection_reasons) if recruiters want to add custom reasons over time? - Should email and calendar integration produce an
application_activities/email_messageslog so all candidate communication is captured against the application? Out of scope for v1. - Should offer approval be modeled as a multi-step approval workflow (e.g. an
offer_approvalsentity with one row per approver) rather than a singleapprover_user_idandpending_approvalstatus? - Should
salary_currencybe promoted from a free-text ISO code to a lookup table or enum once the company hires across multiple geographies? - Should the model track GDPR consent and retention dates explicitly on
candidates(e.g.consent_given_at,retention_expires_at) so automated purging is possible?
7. Implementation notes for the downstream agent
- Create one module named
ats(the module name must equal thesystem_slugfrom the front-matter — do not rename) and two baseline permissions (ats:read,ats:manage) before any entity. - Create entities in dependency order — referenced entities first. Suggested order:
users(deduped against built-in — see step 6)departments(self-references and referencesusers; create the entity, then add the self-ref FK after the entity exists)application_stagescandidate_sourcescandidatesjob_openingsjob_applicationscandidate_documentsapplication_notesinterviewsinterview_feedbackoffershiring_team_members
- For each entity: set
label_columnto the snake_case field marked as label in §3, passmodule_id,view_permission: "ats:read",edit_permission: "ats:manage". Setaudit_log: trueonjob_openings,candidates,job_applications,interview_feedback, andoffers(per §3). Do not manually createid,created_at,updated_at, or the auto-label field. - For each field in §3: pass
table_name,field_name,format,title(the Label column), and forreference/parentfields alsoreference_tableand areference_delete_modeconsistent with §4. The §3Requiredcolumn is analyst intent; the platform manages nullability internally and does not need a per-field flag. Passenum_valuesfor everyenumfield, taken from §5. Setunique_value: trueondepartments.department_name,departments.department_code,job_openings.job_code,application_stages.stage_name,candidate_sources.source_name,candidates.email_address, andusers.email_address. - Fix up each entity’s auto-created label-column field title.
create_entityauto-creates a field whosefield_nameequals the entity’slabel_columnand whosetitledefaults tosingular_label. For most entities in this model, the §3 Label of the label_column field differs fromsingular_labeland the title must be patched withupdate_field. Theupdate_fieldidis the composite string"{table_name}.{field_name}"— pass it as a string, not an integer. Required updates:"departments.department_name"→ title"Department Name""job_openings.job_title"→ title"Job Title""application_stages.stage_name"→ title"Stage Name""candidate_sources.source_name"→ title"Source Name""candidates.full_name"→ title"Full Name""job_applications.application_label"→ title"Application""candidate_documents.document_label"→ title"Document""application_notes.note_subject"→ title"Subject""interview_feedback.feedback_label"→ title"Feedback""hiring_team_members.team_member_label"→ title"Team Member""users.display_name"→ title"Display Name"- (
interviews.interview_labelandoffers.offer_labelalready matchsingular_label“Interview” / “Offer” — no fixup needed.)
- Deduplicate against Semantius built-in tables. This model declares
usersfor self-containment. Semantius ships a built-inuserstable — read it first; if it exists, skip the create and reuse the built-in as thereference_tabletarget everywhere this model points tousers. Optionally add any of the §3.13 fields the built-in lacks (display_name,email_address,first_name,last_name,job_title,department_id,is_active) — additive low-risk only. Do not attempt to recreateusers. Thedepartments,roles,permissions, etc. listed in §3 are not Semantius built-ins and should be created normally. - After creation, spot-check that
label_columnon each entity resolves to a real field, that allreference_tabletargets exist, that theparent_department_idself-reference ondepartmentswas successfully added after the entity was created, and that the two junction FKs onhiring_team_membersresolve correctly.