Skip to main content

Gig::Application

Purpose

An Application represents a talent's intent to work a specific shift, combined with the mutual selection process between employer and talent. The talent shows they want to work the shift by applying, and the employer shows they want that talent by accepting.

Applications belong to the recruiting phase of the gig lifecycle. They answer the question: "Who wants to work this shift, and do both parties agree?" Once both parties agree (the "double handshake"), the Application's job is done and a Gig::Assignment is created for the operations phase.

Applications also carry the talent's ranking data — a score computed from show-up reliability, cancellation history, category experience, and employer ratings. Employers see applicants ordered by rank, and the auto-selection system uses ranking to pick the best candidates when the employer hasn't selected anyone by the deadline.

Model Context

ContextDetails
AggregateGig::Application (independent — child of Shift)
LayerRecruiting
Upstream dependenciesGig::Shift (which shift the talent is applying to), Talent::Profile (who is applying)
Downstream dependentsGig::Assignment (created when application reaches confirmed status)

State Machine

FromToTriggerNotes
(new)pendingUC-1: Talent appliesDefault status. Talent is in the applicant pool.
pendingacceptedUC-3: Employer acceptsEmployer selected this talent. Waiting for talent to confirm.
pendingacceptedUC-4: Auto-selection selectsSystem auto-selected this talent. Still needs talent confirmation.
pendingrejectedUC-5: Employer rejectsEmployer does not want this talent. Reason required.
pendingwithdrawnUC-6: Talent withdrawsTalent changed their mind before selection. No penalty.
pendingexpiredUC-8: Shift cutoff reachedShift expired or cutoff passed without this talent being selected.
pendingcancelledUC-9: Shift cancelledEmployer cancelled the entire shift.
acceptedconfirmedUC-7: Talent confirmsDouble handshake complete. System creates Gig::Assignment.
acceptedexpiredUC-8: Talent confirmation deadline passesTalent didn't confirm in time. Employer can select the next ranked applicant.
accepteddeclinedUC-7: Talent explicitly declinesTalent was selected but says no. No penalty (they were asked, not committed).
acceptedcancelledUC-9: Shift cancelledEmployer cancelled the entire shift.
confirmedcancelledAssignment-level cancellationIf the resulting Assignment is cancelled, the Application reflects this.

Use Cases

IDUse CaseTriggerActor
UC-1Talent applies to a ShiftTalent wants to work this shiftTalent
UC-2System ranks applicants for a ShiftNew application received or ranking refresh neededSystem
UC-3Employer accepts one or more applicantsEmployer selects workers from the ranked listEmployer
UC-4System auto-selects top-ranked applicantsAuto-select deadline reached, employer hasn't selectedSystem
UC-5Employer rejects an applicantEmployer does not want this talent for the shiftEmployer
UC-6Talent withdraws application before selectionTalent changed their mindTalent
UC-7Talent confirms or declines acceptanceTalent responds to selection notificationTalent
UC-8Application expiresShift cutoff or confirmation deadline reachedSystem
UC-9Application cancelled due to shift cancellationEmployer cancels the shiftSystem
UC-10Time overlap auto-rejectionTalent confirmed for a conflicting shiftSystem
UC-11View applicants for a ShiftEmployer reviewing who appliedEmployer

UC-1: Talent applies to a Shift

FieldDetails
ActorTalent::Profile (talent)
TriggerTalent wants to work this shift

Preconditions:

  • Gig::Shift exists and status: open
  • Shift headcount is not yet full (filled_count < headcount)
  • Talent is not suspended (no active suspension)
  • Talent does not have a confirmed Assignment that overlaps with this shift's time
  • Talent has not already applied to this shift

System Behavior:

  1. Talent selects a Shift from the marketplace (via Listings::Job)
  2. System validates preconditions (listed above)
  3. System creates Gig::Application with status: pending
  4. System triggers ranking recalculation for all pending Applications on this Shift (UC-2)

Business Rules:

  • One Application per talent per shift — a talent cannot apply twice to the same shift
  • Applying does not guarantee selection — the talent enters the ranked applicant pool
  • Time overlap check is against confirmed Assignments only (not other pending Applications)
  • Suspended talent cannot apply

Postconditions:

  • Application exists with status: pending
  • Talent appears in the employer's applicant list for this shift

UC-2: System ranks applicants for a Shift

FieldDetails
ActorSystem
TriggerNew application received, or periodic ranking refresh

Preconditions:

  • Shift has at least one pending Application

System Behavior:

  1. System evaluates each pending applicant using a weighted scoring algorithm:
    • Show-up reliability (30%): based on historical no-show and completion rates
    • Cancellation history (25%): frequency and timing of past cancellations
    • Category experience (25%): completed shifts in the same job role/category
    • Employer ratings (20%): average ratings from previous employers
  2. System assigns a talent_rank (1 = best) and stores the score breakdown in talent_rank_detail (JSON)
  3. Ranking is recalculated when a new application arrives or when existing applicants' statuses change

Business Rules:

  • Ranking applies only to pending Applications — accepted/confirmed/rejected applications are not re-ranked
  • New talent with no history receive a baseline score (not penalised for being new, but ranked below proven workers)
  • Ranking detail is stored as JSON for transparency: employers and ops can see why a talent is ranked where they are

Postconditions:

  • All pending Applications on the Shift have updated talent_rank and talent_rank_detail

UC-3: Employer accepts one or more applicants

FieldDetails
ActorOrg::UserProfile (employer)
TriggerEmployer selects workers from the ranked applicant list

Preconditions:

  • Shift status: open
  • At least one Application in pending status
  • Number of selections does not exceed remaining headcount (headcount - filled_count)

System Behavior:

  1. Employer selects one or more applicants from the ranked list
  2. For each selected applicant: Gig::Application.statusaccepted
  3. Each accepted talent receives a notification: "You've been selected for [shift details]. Please confirm."
  4. Remaining pending applicants stay in the pool — they may be selected later if an accepted talent declines or expires

Business Rules:

  • Employer can accept up to headcount - filled_count applicants at a time
  • Acceptance does NOT create an Assignment — the talent must still confirm (double handshake)
  • The employer can accept applicants in multiple batches (e.g., accept 2 now, accept 1 more tomorrow)

Postconditions:

  • Selected Applications are accepted
  • Talent notified, awaiting confirmation

UC-4: System auto-selects top-ranked applicants

FieldDetails
ActorSystem
TriggerAuto-select deadline reached, employer hasn't selected anyone

Preconditions:

  • Auto-selection is enabled for this location (per-location configuration)
  • Shift status: open
  • No Applications are in accepted or confirmed status (employer hasn't acted)
  • Auto-select deadline has arrived:
    • AM shifts (starting 00:00–11:59): 9:00am the previous day
    • PM shifts (starting 12:00–23:59): 5:00pm the previous day

System Behavior:

  1. System retrieves all pending Applications, ordered by talent_rank ascending
  2. System selects the top N applicants where N = headcount
  3. For each: validates no time overlap, talent not suspended
  4. If a talent fails validation, skip to the next ranked applicant
  5. Each valid applicant: Gig::Application.statusaccepted
  6. Remaining unselected Applications: Gig::Application.statusrejected (reason: auto-selection — not selected)
  7. Each accepted talent receives a notification

Business Rules:

  • Auto-selection only runs if NO applicant has been accepted or confirmed — if the employer has already selected at least one person, auto-selection does not trigger
  • Auto-selection still requires talent confirmation (double handshake) — it does not skip to Assignment
  • If there are fewer eligible applicants than headcount, the system selects all available

Postconditions:

  • Top-ranked Applications are accepted
  • Remaining Applications are rejected
  • Talent notified, awaiting confirmation

UC-5: Employer rejects an applicant

FieldDetails
ActorOrg::UserProfile (employer)
TriggerEmployer does not want this talent for the shift

Preconditions:

  • Application status: pending

System Behavior:

  1. Employer rejects the applicant with a mandatory reason (via Taxonomy::GigStatusReason)
  2. Gig::Application.statusrejected
  3. Talent is notified

Business Rules:

  • Rejection reason is mandatory
  • Rejection is final — the talent cannot re-apply to the same shift
  • Rejection does not affect the talent's ranking or suspension status (it's the employer's choice, not a penalty)

Postconditions:

  • Application status: rejected
  • Talent notified

UC-6: Talent withdraws application before selection

FieldDetails
ActorTalent::Profile (talent)
TriggerTalent changed their mind before selection

Preconditions:

  • Application status: pending

System Behavior:

  1. Talent requests withdrawal
  2. Gig::Application.statuswithdrawn
  3. System recalculates rankings for remaining applicants

Business Rules:

  • No penalty, no document required — the talent was not yet committed
  • Cancellation reason is optional
  • The talent can apply to other shifts freely

Postconditions:

  • Application status: withdrawn
  • Shift's applicant count decreases

UC-7: Talent confirms or declines acceptance

FieldDetails
ActorTalent::Profile (talent)
TriggerTalent responds to selection notification

Preconditions:

  • Application status: accepted
  • Confirmation deadline has not passed

System Behavior — Confirms:

  1. Talent confirms: "Yes, I will work this shift"
  2. Gig::Application.statusconfirmed
  3. System creates Gig::Assignment with status: confirmed
  4. System checks for time overlaps with the talent's other pending/accepted Applications → conflicting Applications auto-rejected (UC-10)
  5. Shift's filled count increases

System Behavior — Declines:

  1. Talent declines: "No, I can't work this shift"
  2. Gig::Application.statusdeclined
  3. The position opens up — employer can select the next ranked applicant
  4. Talent is not penalised (they were asked, not committed)

Business Rules:

  • Confirmation must happen within a deadline (configurable — e.g., 24 hours after acceptance, or a fixed time before shift start)
  • Declining is free — no penalty, no suspension impact. The talent was offered the position, they said no.
  • Upon confirmation, the double handshake is complete and the Assignment is created immediately

Postconditions (confirmed):

  • Application status: confirmed
  • Gig::Assignment created
  • Time overlap check executed

Postconditions (declined):

  • Application status: declined
  • Position available for next applicant

UC-8: Application expires

FieldDetails
ActorSystem
TriggerDeadline reached without action

Preconditions:

  • Application status: pending (shift cutoff reached) or status: accepted (talent didn't confirm)

System Behavior — Pending application expires:

  1. Shift cutoff time reached
  2. All remaining pending Applications → expired

System Behavior — Accepted application expires:

  1. Talent confirmation deadline passes
  2. Gig::Application.statusexpired
  3. The position opens up — employer or auto-selection can pick the next applicant
  4. Talent is notified: "Your selection for [shift] has expired because you did not confirm in time"

Business Rules:

  • Expiration is not a penalty — the talent is not penalised for not confirming (they may not have seen the notification)
  • Expired accepted applications free up the position for the next ranked applicant

Postconditions:

  • Application status: expired

UC-9: Application cancelled due to shift cancellation

FieldDetails
ActorSystem
TriggerEmployer cancels the shift

Preconditions:

  • Application in any non-terminal status (pending, accepted, confirmed)

System Behavior:

  1. Employer cancels the Shift (see Gig::Shift UC-4)
  2. All Applications in pending, accepted, or confirmed status → cancelled
  3. Talent are notified: "The shift you applied for / were selected for has been cancelled"

Business Rules:

  • This is a cascading effect of shift cancellation, not a direct action on the Application
  • Talent are not penalised — the cancellation was employer-initiated

Postconditions:

  • All non-terminal Applications for this shift are cancelled

UC-10: Time overlap auto-rejection

FieldDetails
ActorSystem
TriggerTalent confirms an Assignment for a shift that overlaps with other Applications

Preconditions:

  • Talent just confirmed an Application (UC-7), creating an Assignment
  • Talent has other pending or accepted Applications for shifts that overlap in time

System Behavior:

  1. System scans the talent's other Applications
  2. For each Application where the shift's (starts_at, ends_at) overlaps with the newly confirmed shift:
    • Gig::Application.statusrejected (reason: time overlap)
  3. Talent is notified: "Your application for [Shift B] was automatically withdrawn due to a scheduling conflict with [Shift A]"

Business Rules:

  • Only pending and accepted Applications are checked — existing confirmed Assignments are not cancelled (first-confirmed wins)
  • Overlap is determined by comparing starts_at / ends_at of the shifts
  • The rejection reason is recorded as a system-generated Taxonomy::GigStatusReason (time overlap)

Postconditions:

  • Conflicting Applications are rejected
  • Talent notified of each auto-rejection

UC-11: View applicants for a Shift

FieldDetails
ActorOrg::UserProfile (employer)
TriggerEmployer reviewing who applied

Preconditions:

  • Shift exists and has Applications

System Behavior:

  1. Employer views the applicant list for a Shift
  2. System displays Applications grouped by status:
    • pending: ranked by talent_rank, showing score breakdown
    • accepted: awaiting talent confirmation
    • confirmed: double handshake complete, Assignment created
    • rejected / withdrawn / expired / declined: shown in a collapsed section for reference
  3. Each applicant shows: talent name, ranking score, category experience, reliability rating

Business Rules:

  • Employer can accept or reject from this view
  • Only pending applicants can be accepted or rejected
  • The ranked list is the primary decision-making tool for employers

Postconditions:

  • Read-only operation — actions (accept/reject) are separate UCs

Invariants

  1. One Application per talent per shift — a talent cannot apply twice to the same shift
  2. Applications are never hard-deleted
  3. talent_rank and talent_rank_detail are recalculated when the applicant pool changes
  4. Only pending Applications can be accepted or rejected by the employer
  5. Only accepted Applications can be confirmed or declined by the talent
  6. The double handshake is: employer accepts (pendingaccepted) + talent confirms (acceptedconfirmed). Both must happen before an Assignment is created.
  7. Time overlap rejection only affects pending and accepted Applications — confirmed Applications (with Assignments) are not auto-rejected
  8. rejected, withdrawn, expired, declined, and cancelled are terminal states
  9. Rejection reasons are mandatory for employer-initiated rejections and system auto-rejections (time overlap)
  10. Application status reflects the outcome of the recruiting process — it does not track operational concerns (attendance, payment). Those belong to Gig::Assignment.

Model Interactions

Related ModelRelationshipInteraction
Gig::ShiftApplication belongs_to ShiftTalent applies to a specific shift. The shift's headcount determines how many can be selected.
Talent::ProfileApplication belongs_to TalentProfileIdentifies who applied. Talent's history feeds the ranking algorithm.
Gig::AssignmentApplication creates Assignment on confirmationWhen both parties agree (status confirmed), the system creates an Assignment. The Application's job is then done.
Taxonomy::GigStatusReasonApplication references StatusReasonRequired for employer rejections and system auto-rejections. Records why the application ended.
Org::UserProfileEmployer accepts/rejects applicationsEmployers review the ranked list and make selection decisions.

Schema Gaps

GapImpactSuggested Resolution
Current DBML gig_applications uses is_employer_accept / is_talent_accept booleansDoesn't capture the full status lifecycle (withdrawn, declined, expired)Replace with status enum as designed in this spec
No uuid on gig_applicationsInconsistent with other modelsAdd uuid string [unique, not null]
No confirmed_at / accepted_at timestampsCannot track when each handshake step happenedAdd accepted_at timestamp, confirmed_at timestamp
No declined status in current DBML enumCannot distinguish "talent said no" from "talent didn't respond"Add declined to the status enum
No taxonomy_gig_status_reason_id on gig_applicationsCannot record why an application was rejectedAdd taxonomy_gig_status_reason_id bigint [null]
gig_shift_id typed as integer instead of bigintInconsistent with other FK columnsChange to bigint
Ref type on gig_shift_id is < instead of >Incorrect relationship direction in DBMLChange to ref: > gig_shifts.id