Auburn MyGov Docs Gitea

Module

Programs & Registration

Parks, Recreation, and Library programs — scheduled activities with time-bound sessions, location assignments, and optional citizen registration. The Programs module is an anchor consumer of both the Location layer (where sessions are held) and the Calendar layer (when they run and how they appear publicly).

Status In Progress
Last updated
Related LayerLocation · Calendar & Time · Distribution & Publishing
PlatformMicrosoft Platform
ModuleReservations · Forms
UI Components program-list / program-card — public-facing program browser and detail card; tied to CityProgram / ProgramSession

What the Module Manages

A Program is a titled, categorised activity — a swim class, a summer camp, a library story time. A Program has one or more Sessions, each of which is time-bound (start date, end date, recurrence rule) and location-bound (a FacilitySpace). Sessions are the unit of visibility in the Calendar layer and the unit of registration.

Programs belong to a ProgramCategory. Categories map to Calendars (one category → one calendar). The Calendar layer aggregates these into parent calendars (e.g. all Parks categories → "Parks Programs") for unified display. See Calendar & Time.

ProgramCategory
data schema
  • name
  • departmentId (FK)
  • calendarId (FK) → Calendar layer
  • parentCategoryId (nullable)
Program
data schema
  • title, description, imageUrl
  • programCategoryId (FK)
  • ageMin, ageMax, capacity
  • registrationOpen / Close
  • requiresRegistration
  • isPublished
ProgramSession
data schema
  • programId (FK)
  • dtstart, dtend, rrule
  • exdate (JSON) — exceptions
  • facilitySpaceContactId ← WRONG
  • facilitySpaceId ← target FK
  • exchangeEventIds (JSON)
ProgramRegistration
data schema
  • programSessionId (FK)
  • citizenId (FK) or guest info
  • status, registeredAt
  • waitlisted flag

Location Integration

Sessions are held at a FacilitySpace — the stable spatial identity record (see Location). The FK on ProgramSession must be FacilitySpaceId, not FacilitySpaceContactId.

Current state: ProgramSession.facilitySpaceContactId references the CMS display record (cms.FacilitySpaceContact). This is incorrect — a session is held at a space, not at a display record. Display information is joined in from FacilitySpaceContact at render time; the FK should anchor to the stable spatial identity. Migration to ProgramSession.facilitySpaceId is in progress.

Why FacilitySpaceId and not FacilitySpaceContactId

The decision rule is: a record should FK to the entity that describes what it is about, not the entity that happens to have a field you need right now. A ProgramSession is about being held at a physical space. The FacilitySpace is the authoritative identity of that space. FacilitySpaceContact is a display record whose content is derived from FacilitySpace; it is a view, not an identity. If FacilitySpaceContact is replaced, regenerated, or split, ProgramSession records should not be affected — which is only true if the FK is on FacilitySpace.

How location is displayed

When rendering a session on the calendar or program detail page, the display pipeline is:

  1. Join ProgramSession.facilitySpaceId → FacilitySpace to get the canonical space name and facilityId.
  2. Join FacilitySpace.facilityId → Facility to get the facility name and address.
  3. Optionally join FacilitySpaceContact to get display overrides (name override, photos, amenities notes).
  4. Render: "Auburn Recreation Center — Main Pool" with a link to the facility page.

Calendar Integration

A published Program's sessions must appear on the Calendar immediately. The current state is a manual sync trigger; the design target is on-publish auto-push.

Design target: on-publish, synchronous push. When a Program is published (or when a Session is added/updated/deleted on a published program), the corresponding CalendarEvent row is written in the same request. Existing endpoint: POST /programs/{id}/sync-calendar. What is missing: the publish action calling this endpoint automatically without staff having to trigger it manually.

Session → CalendarEvent mapping

Each ProgramSession maps to one CalendarEvent (or one per recurrence instance under Option A; one entry with rrule under Option B — see Calendar & Time). The CalendarEvent is written to the calendar mapped to the session's program category.

CalendarEvent fieldSource
uid"programSession-{session.id}" — stable, enables upsert
summaryProgram.title
dtstart / dtendProgramSession.dtstart / dtend
rruleProgramSession.rrule
locationAuto-composed: "Facility Name — Space Name, Address"
urlCMS program detail page URL
facilitySpaceIdProgramSession.facilitySpaceId (target; currently facilitySpaceContactId)
sourceType"programSession" (Option B target — not currently set)
sourceIdProgramSession.id (Option B target — not currently set)
imageUrlProgram.imageUrl (Option B target — not currently set)

Exchange blocking

Where a FacilitySpace has an Exchange resource mailbox configured (FacilitySpace.exchangeResourceMailbox), a confirmed program session block is written to the mailbox to prevent double-booking from Outlook. This is the same mechanism used by the Reservations module. The ProgramSession.exchangeEventIds JSON field stores the Exchange event IDs for correlation on update/delete.

Registration

Internal registration

The MyGov platform has a native ProgramRegistration entity. A citizen (authenticated or guest) registers for a session; a registration record is created linking citizen → session. Capacity enforcement and waitlisting are handled by the API. Confirmation is sent via the Workflow Engine / transactional email.

Internal registration is currently used by the Library for library-specific programs. It is the target state for any program module that does not go to an external platform.

External registration — Parks (Vega / ActiveNet)

Parks programs are currently registered through an external platform (Vega / ActiveNet). MyGov's role is display and linking only — the website shows the program and links to the external platform for checkout. MyGov does not receive registration data from the external platform.

Direction: internal module is the preferred end state. If Parks registration moves in-house, it uses the same ProgramRegistration entity and UI that Library already uses. If Parks stays on Vega/ActiveNet long-term, the integration model is display+link — MyGov is not a wrapper around the external platform, and the external platform does not push registrant data into MyGov unless there is a concrete use case that requires it.

Library registration

Library programs are managed internally in MyGov. The current Library-specific registration uses the ProgramRegistration entity. If the Library adopts an external library management system (e.g. Vega), the same display+link integration model applies — the goal is an internal module that works for both Parks and Library; if one or both go external, integration is preferred over replacement.

Waitlist

When a session is at capacity, additional registrations are placed on a waitlist (ProgramRegistration.waitlisted = true). When a spot opens, the next waitlisted registration is promoted automatically (Workflow Engine trigger). A confirmation notification is sent to the newly confirmed registrant. This is a design target — the promotion trigger is not yet implemented.

Relationship with Reservations

Programs and Reservations both occupy FacilitySpaces at specific times. They are separate operational concepts but share the same availability surface — a space cannot host a Program session and a citizen Reservation at the same time.

Conflict resolution: the Reservations module queries for availability using a FacilitySpace + time window query. That query must include both FacilitySpaceReservation rows and ProgramSession rows for the same space. Currently only reservations are checked — the program session overlap check is a known gap. See Reservations.

Audit Findings

FindingPriorityDetail
ProgramSession.facilitySpaceContactId wrong FK MEDIUM-HIGH Should be facilitySpaceId (spatial identity). Migration in progress. Blocks correct CalendarEvent location display and availability conflict checks.
On-publish sync not automatic MEDIUM POST /programs/{id}/sync-calendar exists but is not called on publish. Staff must trigger manually. Before go-live, wire the publish action to call sync.
Program sessions not checked in reservation availability query MEDIUM A reservation can be confirmed for a FacilitySpace that already has a program session at that time. Availability query must union ProgramSession rows. Tracked in Reservations.
CalendarEvent Option B fields not populated LOW sourceType, sourceId, facilitySpaceId, imageUrl on CalendarEvent are not set during program sync (these fields don't exist yet — Option B migration required first).
Waitlist promotion trigger not implemented LOW Waitlisted registrations are created correctly but automatic promotion on cancellation is not wired.
Exchange blocking for program sessions LOW Phase 2 of sync-calendar; requires FacilitySpaceReservationConfig migration. Not a near-term priority unless staff double-booking via Outlook is an active problem.

Open Questions

  • Parks registration timeline. Is there a target date or trigger for Parks moving registration in-house? If Vega/ActiveNet is long-term, what (if any) data should be synced into MyGov from the external platform?
  • Library management system. Is Vega being evaluated for the Library? If so, does it displace internal Programs registration for Library programs, or coexist alongside it?
  • Program availability visibility. Should the public program detail page show current availability (spots remaining) in real time, or just show "registration open/closed"? This affects the API query pattern for the public-facing display.
  • Registrant notification for program changes. When a program session is rescheduled or cancelled, registered attendees should receive a notification. Which channel (transactional email via Workflow, eNotifier campaign, or both)? Who triggers it — automatic on status change, or staff-confirmed?
  • Parks new program notification. When Parks publishes a new program, how should subscribers be notified — auto-push per publish, a weekly staff-composed digest, or staff-triggered per campaign? Resolve with Parks before building eNotifier automation for programs.
  • Cross-department programs. Are there programs that span multiple departments or categories? If so, does the current single programCategoryId FK support this?