Lottery workflow
Listing closes
When a listing’s
applicationDueDate passes (or a partner manually closes it), the listing status moves to closed. The reviewOrderType must be set to lottery for the lottery workflow to apply.Resolve flagged sets
Before running the lottery, partners should resolve any flagged duplicate application sets. Applications with
markedAsDuplicate: true are automatically excluded from lottery randomization.Generate lottery results
An admin calls
PUT /lottery/generateLotteryResults (or uses the Partners Portal). The service:- Deletes any existing
applicationLotteryPositionsandapplicationLotteryTotalrecords for the listing (to support re-runs) - Fetches all non-deleted, non-duplicate applications
- Assigns a random ordinal to each application via
lotteryRandomizer() - Stores positions in
applicationLotteryPositions - Computes preference-specific sub-rankings for each
MultiselectQuestionwith sectionpreferences - Sets
lotteryStatustoran
Review results
Partners download the lottery export via
GET /lottery/getLotteryResults to review the ranked list before publishing. The export includes per-application ordinal ranks and preference sub-ranks.Publish results
Partners or admins call
PUT /lottery/lotteryStatus with lotteryStatus: publishedToPublic. This makes results visible to applicants on the public portal. The lotteryLastPublishedAt timestamp is recorded on the listing. Notification emails are sent to applicants.Applicants view results
Applicants use
GET /lottery/publicLotteryResults/:id (where id is their application UUID) to see their lottery position across all preference pools and the general pool. GET /lottery/lotteryTotals/:id (where id is the listing UUID) returns total applicant counts per preference pool.Lottery statuses
TheLotteryStatusEnum (from @prisma/client) tracks the lottery’s progress independently of the listing status:
| Status | Description |
|---|---|
ran | Lottery has been generated; results are not yet public |
approved | Results have been reviewed and approved internally |
publishedToPublic | Results are visible to applicants |
expired | Lottery data has been expired per the LOTTERY_DAYS_TILL_EXPIRY schedule |
errored | Lottery generation encountered an error |
retracted | Previously published results have been retracted |
rerun and retracted states are tracked in the activity log as LotteryActivityLogStatus values.
Lottery preferences
Preferences are multiselect questions assigned to a listing withapplicationSection: preferences. When the lottery is generated, the service computes a separate ranked sub-list for each preference:
- All applications that selected the preference option are extracted
- They are sorted by their general lottery ordinal (already randomized)
- A preference-specific
applicationLotteryPositionsrecord is created with themultiselectQuestionIdset
When
enableGeocodingPreferences is active on the jurisdiction, preference eligibility can be verified automatically using the applicant’s geocoded address. Only applicants whose address falls within the defined geographic boundary are counted as qualifying for the preference.enableGeocodingRadiusMethod flag enables a radius-based alternative: the applicant’s address is compared against a center point and a mile radius rather than a polygon boundary.
Waitlist vs. open lottery
- Lottery (open listing)
- Waitlist lottery
The standard lottery applies to listings with
reviewOrderType: lottery. All eligible applications are randomized together. Results are ranked 1 through N.Auto-publishing results
Bloom includes an automated publish cron job controlled byLOTTERY_PUBLISH_PROCESSING_CRON_STRING. When triggered, PUT /lottery/autoPublishResults finds listings whose lottery results meet the criteria for automatic publication and sets their lotteryStatus to publishedToPublic.
This job requires admin or jurisdictional admin authorization and is typically scheduled via environment configuration.
Lottery data expiry
Lottery data is automatically expired after a configurable number of days using theLOTTERY_DAYS_TILL_EXPIRY environment variable. The expiry cron runs on the schedule defined by LOTTERY_PROCESSING_CRON_STRING.
When the cron runs (PUT /lottery/expireLotteries), it:
- Finds all
closedlottery listings whoseclosedAtdate is older thanLOTTERY_DAYS_TILL_EXPIRYdays - Creates a snapshot of each listing via
SnapshotCreateServicebefore expiring - Sets
lotteryStatustoexpiredon all qualifying listings - Writes an activity log entry for each expired lottery
Activity log
Every lottery status change is recorded in the activity log with modulelottery. The GET /lottery/lotteryActivityLog/:id endpoint returns the full audit trail for a listing’s lottery, including who made each status change and when.
Activity log statuses tracked (LotteryActivityLogStatus): ran, approved, publishedToPublic, expired, errored, rerun, retracted, closed.
API endpoints
| Method | Path | Description |
|---|---|---|
PUT | /lottery/generateLotteryResults | Generate (or re-generate) lottery results for a listing |
PUT | /lottery/lotteryStatus | Change the lottery status for a listing |
GET | /lottery/getLotteryResults | Export lottery results as a ZIP (requires partner auth) |
GET | /lottery/getLotteryResultsSecure | Export lottery results as a signed URL |
GET | /lottery/publicLotteryResults/:id | Get an applicant’s lottery positions by application UUID |
GET | /lottery/lotteryTotals/:id | Get total applicant counts per pool for a listing |
GET | /lottery/lotteryActivityLog/:id | Get the lottery activity log for a listing |
PUT | /lottery/autoPublishResults | Trigger the auto-publish cron job |
PUT | /lottery/expireLotteries | Trigger the lottery expiration cron job |
PUT /lottery/generateLotteryResults requires the requesting user to have the isAdmin role. Only system administrators can initiate lottery generation.Lottery exports for partners
The lottery export (GET /lottery/getLotteryResults) reuses the application exporter with lottery-specific flags enabled. The export:
- Includes all applications (not just those with positions)
- Adds lottery ordinal rank columns to the output
- Adds per-preference sub-rank columns for each preference attached to the listing
- Requires an API key and partner-level permissions via
ExportLogInterceptor
GET /lottery/getLotteryResultsSecure) returns a signed URL for deferred download.