FHIR to Oscar Mapping¶
FHIR_To_Oscar_Mapping:
# This mapping governs the write-back path:
# Jarvis (or any FHIR client) POSTs a NexusEmrCoreInvoice → Oscar creates
# billing_on_cheader1 + billing_on_item rows.
# Also covers read-back round-trip fidelity via the existing
# InvoiceConverter.toOscarObject(Invoice) direction.
NexusEmrCoreInvoice:
id: "billing_on_cheader1.id (server-assigned on POST)"
identifier: "billing_on_cheader1.header_id (when present)"
identifier[rawCode]: "billing_on_cheader1.header_id or provider_ohip_no or hin (per identifier system)"
status: "See status table below (+ oscarBillingStatus extension takes precedence if present)"
extension[oscarBillingStatus]: "**Primary** source for billing_on_cheader1.status when set (carries raw 10-code Oscar enum)"
extension[shadowBill]: "Informational — derived from billing_on_cheader1.status='H'; Jarvis should not set this on write (write sets status='H' and shadowBill is derived on read)"
type.coding[payProgram].code: "billing_on_cheader1.pay_program (moved from extension[payProgram] in 1.9.9)"
extension[admissionDate]: "billing_on_cheader1.admission_date"
extension[appointmentNo]: "billing_on_cheader1.appointment_no"
extension[facilityNum]: "billing_on_cheader1.facilty_num (sic)"
extension[serviceLocation].valueReference: "Reference(NexusEmrCoreLocation) resolving billing_on_cheader1.location — SLI code carried on Location.identifier[sliCode] (refactored from string extension in 1.9.9)"
extension[manualReview]: "billing_on_cheader1.man_review"
extension[paid]: "billing_on_cheader1.paid"
extension[province]: "billing_on_cheader1.province (always 'ON' in Phase 1)"
extension[visitType]: "billing_on_cheader1.visittype"
subject.reference: "billing_on_cheader1.demographic_no (strip 'Patient/' prefix)"
issuer.reference: "billing_on_cheader1.clinic"
date: "billing_on_cheader1.billing_date + billing_time"
participant[role=billing].actor: "billing_on_cheader1.provider_no"
participant[role=appointment].actor: "billing_on_cheader1.apptProvider_no"
participant[role=assistant].actor: "billing_on_cheader1.asstProvider_no"
participant[role=referring].actor: "billing_on_cheader1.ref_num"
totalGross.value: "billing_on_cheader1.total"
note[0].text: "billing_on_cheader1.comment1"
# -------------------------------------------------------------------------
# Invoice.status (canonical FHIR) → billing_on_cheader1.status
# When oscarBillingStatus extension is present, it wins.
# Otherwise default mapping from the canonical FHIR code.
# -------------------------------------------------------------------------
Invoice_status_reverse:
issued: "If payProgram=PAT → 'P'; payProgram=WCB → 'W'; else 'O' (Bill OHIP default)"
balanced: "If shadowBill=true → 'H' (Capitated); else 'S' (Settled)"
cancelled: "If oscarBillingStatus=X → 'X' (Bad Debt); else 'D' (Deleted)"
"entered-in-error": "'N' (Do Not Bill)"
draft: "**Not currently used** — reserved for a future provider-review intermediate state, not yet scoped"
# -------------------------------------------------------------------------
# Invoice.lineItem[] → billing_on_item rows
# -------------------------------------------------------------------------
Invoice_lineItem:
id: "billing_on_item.id (server-assigned on POST)"
sequence: "**Not stored** — used only for ordering on FHIR side"
chargeItemCodeableConcept.coding[ohip].code: "billing_on_item.service_code"
priceComponent[type=base].amount.value: "billing_on_item.fee (serialize as String)"
priceComponent[type=base].factor: "billing_on_item.ser_num (serialize as String)"
extension[DIAGNOSTIC_1].valueCodeableConcept: "billing_on_item.dx (primary)"
extension[DIAGNOSTIC_2].valueCodeableConcept: "billing_on_item.dx1 (secondary)"
extension[DIAGNOSTIC_3].valueCodeableConcept: "billing_on_item.dx2 (tertiary) — note typo in extension URL (diagnositc3)"
extension[SERVICE_DATE].valueDate: "billing_on_item.service_date"
extension[STATUS].valueString: "billing_on_item.status ('D' = soft-delete; filtered out of BillingONCHeader1.billingItems collection via @Where at BillingONCHeader1.java:190)"
extension[HEADER_ID].valueInteger: "billing_on_item.ch1_id (= parent Invoice's billing_on_cheader1.id)"
extension[LAST_UPDATED].valueDateTime: "billing_on_item.timestamp"
# -------------------------------------------------------------------------
# NexusEmrCorePractitioner → provider
# -------------------------------------------------------------------------
NexusEmrCorePractitioner:
identifier.value: "provider.provider_no (PK)"
identifier[ohipBillingNumber].value: "provider.ohip_no (NEW slice — required for providers billing OHIP)"
identifier[rmaNumber].value: "provider.rma_no (NEW slice — optional; RMA / group billing)"
identifier[rawCode]: "provider.billing_no, provider.practitionerNo (by system URI)"
name.family: "provider.last_name"
name.given: "provider.first_name"
name.prefix: "provider.title"
gender: "provider.sex"
birthDate: "provider.dob"
address.line: "provider.address"
address.city: "provider.city"
address.state: "provider.province"
address.postalCode: "provider.postalCode"
telecom[use=home].value: "provider.phone"
telecom[use=work].value: "provider.work_phone"
telecom[system=email].value: "provider.email"
active: "provider.status ('1' ↔ true)"
qualification: "provider.credentials"
# -------------------------------------------------------------------------
# NexusEmrCorePatient → demographic (write-back scenarios rare for Jarvis)
# -------------------------------------------------------------------------
NexusEmrCorePatient:
identifier.value: "demographic.demographic_no"
identifier[jurisdictional].value: "demographic.hin"
identifier[jurisdictional].extension[hcnVersion]: "demographic.ver"
name.family: "demographic.last_name"
name.given: "demographic.first_name"
birthDate: "demographic.year_of_birth + month_of_birth + date_of_birth (all String columns)"
gender: "demographic.sex"
address: "demographic.address / city / province / postal"
telecom: "demographic.phone / phone2 / email"
active: "demographic.patient_status ('AC' ↔ true)"
extension[rosterStatus]: "demographic.roster_status"
# -------------------------------------------------------------------------
# NexusEmrCoreCondition → dxresearch or casemgmt_issue (depending on category)
# -------------------------------------------------------------------------
NexusEmrCoreCondition:
"category=problem-list-item from dxresearch source":
id: "dxresearch.dxresearch_no"
subject: "dxresearch.demographic_no"
code.coding.code: "dxresearch.dxresearch_code"
code.coding.system: "dxresearch.coding_system (must reverse-map $ICD9CM → 'icd9' for Oscar write)"
onsetDateTime: "dxresearch.start_date"
recorder: "dxresearch.providerNo" # column is camelCase — DxResearch.java:50 has no @Column override
clinicalStatus: "dxresearch.status (active→A, inactive→C)"
"category=problem-list-item from casemgmt_issue source (Medical History)":
id: "casemgmt_issue.id"
subject: "casemgmt_issue.demographic_no"
code: "Looked up via casemgmt_issue.issue_id → Issue catalog"
type: "casemgmt_issue.type = 'MedHistory' (or confirmed value)"
clinicalStatus: "casemgmt_issue.resolved (true→resolved, false→active)"
# -------------------------------------------------------------------------
# Unsupported / deferred (Phase 1)
# -------------------------------------------------------------------------
unsupported_phase1:
Claim: "Payer submission — Oscar submits to OHIP via H-file, not FHIR Claim"
ClaimResponse: "Payer adjudication — deferred to Phase 2 if rejection loop is scoped in"
ExplanationOfBenefit: "Patient-facing — Jarvis is practitioner-facing"
Coverage: "No multi-payer filtering in Phase 1"
Account: "Patient ledger — not a Phase 1 use case"
ChargeItem_standalone: "Not needed — inline Invoice.lineItem[] suffices"
ChargeItemDefinition: "OHIP fee catalog exposed via REST (/api/billing/service), not FHIR"
CoverageEligibilityRequest: "No pre-service eligibility check in Phase 1"
Write-back Implementation Checklist¶
Required behaviour for InvoiceConverter.toOscarObject(Invoice) and the new R4 InvoiceResourceProvider POST handler. Depends on the write-back design decisions (state machine, idempotency, draft state).
Identity + idempotency¶
- [ ] Honour
If-None-Existheader lookup onInvoice.identifier[rawCode]to detect duplicate submissions from Jarvis retries - [ ] Accept a Jarvis-supplied recommendation UUID as an additional
Invoice.identifierslice; persist to a new column or dedup table (design not yet settled)
Status semantics¶
- [ ]
oscarBillingStatusextension takes precedence over the canonical FHIRInvoice.statuswhen both are present (preserves the full 10-code Oscar enum) - [ ] Default mapping when only
Invoice.statusis set (seefhir-to-oscar.yamlInvoice_status_reversesection) - [ ] Handle
Invoice.status = draft— either map to a new Oscar status code (e.g.Jfor Jarvis-draft), map to an existing code and carry the draft flag on a separate column, or reject draft POSTs in Phase 1 (not yet settled)
Line item parsing¶
- [ ] Parse
lineItem.priceComponent.amount.value(BigDecimal) back to Oscarbilling_on_item.fee(String — preserve source formatting) - [ ] Parse
lineItem.priceComponent.factor(positiveInt) back to Oscarbilling_on_item.ser_num(String) - [ ] Map
lineItem.chargeItemCodeableConcept.coding[system=$OhipSoB].code→billing_on_item.service_code - [ ] Map
lineItem.extension[DIAGNOSTIC_1/2/3]→ Oscardx/dx1/dx2(preserve thediagnositc3URL typo during lookup until it is fixed)
Coding-system reverse mapping¶
- [ ] Reverse
$ICD9CMURI → literal"icd9"on dxCoding.systemfor round-trip fidelity withdxresearch.coding_system - [ ] Reverse
$OhipSoBURI — decide whether round-trip writes need to scrub to Oscar's internal system label or preserve the URI
Provider + participant handling¶
- [ ] Snapshot
provider_ohip_no+provider_rma_noonbilling_on_cheader1at write time (same as existing converter does on read) - [ ] Resolve
participant[role=billing].actor.reference→billing_on_cheader1.provider_no - [ ] Accept
NexusEmrCoreOrganizationas participant actor (clinic) — added in v7 of profile; reverse mapping currently missing inInvoiceConverter.toOscarObject
Patient + encounter linkage¶
- [ ] Resolve
Invoice.subject.reference(Patient/<id>) →billing_on_cheader1.demographic_no - [ ] Resolve
Invoice.extension[appointmentNo].valueInteger→billing_on_cheader1.appointment_no - [ ] Optionally resolve an
Encounterreference if one is present (not currently in the mapping)
Notes + extensions¶
- [ ] Write
Invoice.notetext tobilling_on_cheader1.comment1(handle multi-note concatenation if present — FHIR allows multiple notes; Oscar has one field) - [ ] Preserve the 12
apps.health/Invoice/*header extensions and 6apps.health/InvoiceLineItem/*line-item extensions round-trip (already handled by existing converter; confirm no regression)
Validation + error handling¶
- [ ] Validate
Invoice.statusis in the canonical 5-value FHIR set (server-side) - [ ] Replace the current
default:branch inInvoiceConverter.getInvoiceStatus(InvoiceConverter.java:535-539) that throwsInvalidRequestException— on unknown Oscar status, log + map tonullinstead - [ ] Return a
ClaimResponseorOperationOutcomeon rejection (Phase 1 decision: may be out of scope)
Out of scope for Phase 1¶
- Real-time adjudication feedback (
ClaimResponse) - Multi-tenant write-back across clinic boundaries
- FHIR batch/transaction bundle write-back (one Invoice per POST for now)