Oscar → FHIR Mapping (Schedule + Slot)¶
This page describes how OscarPro's scheduletemplate family of tables maps to the new NexusEmrCoreSchedule and NexusEmrCoreSlot profiles. The structured mapping is in oscar-to-fhir.yaml; this page is the narrative + edge-case companion.
Phase 1 status (2026-05-26): OscarPro already has partial Schedule/Slot converter code committed (
ScheduleConverter.java,SlotConverter.java,ScheduleTemplateCodeConverter.java). The migration spec inconverter-contracts.mdenumerates the 10 deltas required to bring it into profile compliance. This mapping doc describes the target state once the migration is complete.
Source tables (OscarPro)¶
| Table | Role |
|---|---|
scheduletemplate |
One row per (provider_no, template_name). Carries the timecode string. |
scheduletemplatecode |
Lookup of single-character code → description, duration, color, bookingLimit. |
scheduledate |
Assigns a template to a (provider_no, sdate) tuple. Carries available (within-day blockout flag) and reason. |
scheduleholiday |
Holiday calendar — keyed by sdate, with holiday_name. |
appointment |
Booked appointments — used for the busy/free overlay on Slots. |
Materialisation model¶
The converter produces:
- One
Scheduleresource per(provider_no), covering the rolling planning horizon (typically 90 days). Multiplescheduletemplaterows for the same provider collapse into one Schedule — the template'ssummarymay land inSchedule.comment. - One
Slotresource per non-_character oftimecode, perscheduledaterow. A 96-character timecode with 24 non-_chars produces 24 Slots per day.
Timecode arithmetic¶
The timecode string is interpreted as a per-15-minute grid (or other granularity — the existing converter uses slotDuration = 1440 / timecode.length()). For position i (0-indexed) in a timecode of length L:
slot.start = scheduledate.sdate (at 00:00 local) + (i × (1440 / L)) minutesslot.end = slot.start + (1440 / L) minutesslot.serviceType.coding[rawCode]= lookupscheduletemplatecodebycode = timecode[i]
If timecode[i] == '_', no Slot is emitted at that position — '_' is the "no slot here" convention.
Midnight overflow¶
LocalTime.plusMinutes() wraps at midnight, which corrupts the end timestamp of the last slot in a full 24-hour grid. The migrated converter must use LocalDateTime + Duration arithmetic to advance the date correctly. See converter-contracts.md §"SlotConverter.java" item 5 for the fix.
Timezone resolution¶
Slot.start and Slot.end are FHIR instant values (UTC with offset). The source data is clinic-local; the converter derives the timezone from the clinic's Location.address province via a lookup table:
| Province | IANA TZ |
|---|---|
| ON | America/Toronto |
| AB | America/Edmonton |
| BC | America/Vancouver |
(etc. — see converter-contracts.md) |
ZoneId.systemDefault() MUST NOT be used.
Slot status — busy/free overlay¶
The converter computes Slot.status as follows:
- Default:
free - If
scheduledate.available != '1'(within-day blockout):busy-unavailable, withscheduledate.reasoncarried inSlot.comment -
Otherwise, query the
appointmenttable for rows where:appointment.status NOT IN ('cancelled', 'noshow')(appointment.start_time, appointment.end_time)overlaps[Slot.start, Slot.end)
If any row matches:
busy
The converter never writes busy-tentative — that status belongs to the booking system. See Booking Flow Design for the full status state machine.
Holiday handling¶
If the date appears in scheduleholiday, the converter emits zero Slots for the entire day. This is preferred over emitting a stream of busy-unavailable Slots (which would create dozens of phantom resources for every grid position). The absence of Slots is the signal that the clinic isn't bookable.
Slot identifier strategy — determinism¶
Slots are derived, not stored. To support cache-by-reference and idempotent re-emission, Slot.id and Slot.identifier.value use:
<provider_no>-<yyyyMMdd>-<slot-index>
Example: 42-20260601-36 is the 36th slot in Dr. Navarro's June 1, 2026 schedule. Re-running the converter for the same (provider, date) produces identical resources.
What's NOT mapped (intentionally)¶
| Field | Why |
|---|---|
scheduletemplatecode.color |
UI hint; not clinically meaningful for booking |
scheduletemplatecode.bookingLimit |
Not surfaced in Phase 1; reconsider if overbooking semantics need explicit constraint |
scheduletemplate.summary |
Not mapped on Schedule.comment in Phase 1 (could be added if useful) |
scheduledate.creator / scheduledate.priority |
Operational metadata, not booking-relevant |
scheduletemplatecode.duration (as a per-Slot field) |
Slot duration is computed from timecode.length(), not the per-code duration. The per-code duration is OscarPro's intra-grid hint, not a Slot-level field. |
Worked example¶
See NexusEmrSchedule-example.json and the three Slot examples (free, busy, busy-tentative) for the wire format the migrated converter targets.
Related¶
fhir-to-oscar.md— reverse mapping (booking writes flowing back to OscarPro)converter-contracts.md— the 10 specific deltas the migrated converter must applyoscar-template-schema-notes.md— findings on the source schema