patient resource list and card (#501)

This commit is contained in:
David Radcliffe 2024-08-13 00:42:59 -04:00 committed by GitHub
parent 7b7a0055d7
commit 4642223657
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 507 additions and 59 deletions

View File

@ -22,6 +22,7 @@ import {MedicationComponent} from './resources/medication/medication.component';
import {MedicationRequestComponent} from './resources/medication-request/medication-request.component';
import {ObservationComponent} from './resources/observation/observation.component';
import {OrganizationComponent} from './resources/organization/organization.component';
import {PatientComponent} from './resources/patient/patient.component';
import {PractitionerComponent} from './resources/practitioner/practitioner.component';
import {ProcedureComponent} from './resources/procedure/procedure.component';
import {FhirCardComponent} from './fhir-card/fhir-card.component';
@ -67,6 +68,7 @@ import { ObservationVisualizationComponent } from './common/observation-visualiz
MedicationRequestComponent,
ObservationComponent,
OrganizationComponent,
PatientComponent,
PractitionerComponent,
ProcedureComponent,
@ -108,6 +110,7 @@ import { ObservationVisualizationComponent } from './common/observation-visualiz
MedicationRequestComponent,
ObservationComponent,
OrganizationComponent,
PatientComponent,
PractitionerComponent,
ProcedureComponent,

View File

@ -29,6 +29,7 @@ import {LocationComponent} from '../resources/location/location.component';
import {OrganizationComponent} from '../resources/organization/organization.component';
import {ObservationComponent} from '../resources/observation/observation.component';
import {EncounterComponent} from '../resources/encounter/encounter.component';
import {PatientComponent} from '../resources/patient/patient.component';
@Component({
@ -152,6 +153,9 @@ export class FhirCardComponent implements OnInit, OnChanges {
case "Organization": {
return OrganizationComponent;
}
case "Patient": {
return PatientComponent;
}
case "Procedure": {
return ProcedureComponent;
}

View File

@ -1,9 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ImmunizationComponent } from './immunization.component';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { RouterTestingModule } from '@angular/router/testing';
import { ImmunizationComponent } from './immunization.component';
describe('ImmunizationComponent', () => {
let component: ImmunizationComponent;
let fixture: ComponentFixture<ImmunizationComponent>;

View File

@ -1,13 +1,13 @@
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
import {FhirCardComponentInterface} from '../../fhir-card/fhir-card-component-interface';
import {Router, RouterModule} from '@angular/router';
import {ImmunizationModel} from '../../../../../lib/models/resources/immunization-model';
import {TableRowItem, TableRowItemDataType} from '../../common/table/table-row-item';
import * as _ from "lodash";
import {NgbCollapseModule} from "@ng-bootstrap/ng-bootstrap";
import { CommonModule } from "@angular/common";
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
import * as _ from "lodash";
import { ImmunizationModel } from '../../../../../lib/models/resources/immunization-model';
import { BadgeComponent } from "../../common/badge/badge.component";
import { TableRowItem, TableRowItemDataType } from '../../common/table/table-row-item';
import { TableComponent } from "../../common/table/table.component";
import { FhirCardComponentInterface } from '../../fhir-card/fhir-card-component-interface';
@Component({
standalone: true,

View File

@ -0,0 +1,73 @@
<div class="patient-card card">
<div class="card-header bg-fasten-purple">
<h2 class="mb-0 text-white">{{ displayModel.patient_name }}</h2>
</div>
<div class="card-body">
<div class="row">
<!-- Basic Information -->
<div class="col-md-6 mb-4">
<h3 class="h5 mb-3">Basic Information</h3>
<ul class="list-unstyled">
<li><strong>Date of Birth:</strong> {{ displayModel.patient_birthdate | date:'mediumDate' }}</li>
<li><strong>Age:</strong> {{ displayModel.patient_age }}</li>
<li><strong>Gender:</strong> {{ displayModel.patient_gender | titlecase }}</li>
<li><strong>Birth Sex:</strong> {{ displayModel.birth_sex }}</li>
<li><strong>Marital Status:</strong> {{ displayModel.marital_status }}</li>
<li><strong>Race:</strong> {{ displayModel.race }}</li>
<li><strong>Ethnicity:</strong> {{ displayModel.ethnicity }}</li>
</ul>
</div>
<!-- Contact Information -->
<div class="col-md-6 mb-4">
<h3 class="h5 mb-3">Contact Information</h3>
<ul class="list-unstyled">
<li><strong>Address:</strong>
<address class="mb-0 ml-3">
<div *ngFor="let line of displayModel.patient_address">{{line}}</div>
</address>
</li>
<li><strong>Phone{{ displayModel.patient_phones.length > 0 ? '' : 's' }}:</strong>
<ul class="list-unstyled ml-3">
<li *ngFor="let phone of displayModel.patient_phones">{{ phone.value }} ({{ phone.use }})</li>
</ul>
</li>
<li><strong>Language{{ displayModel.communication_languages.length > 0 ? '' : 's' }}:</strong>
<span *ngFor="let language of displayModel.communication_languages; let last = last">
{{ language.display }}{{last ? '' : ', '}}
</span>
</li>
</ul>
</div>
<!-- Additional Information -->
<div class="col-md-6 mb-4">
<h3 class="h5 mb-3">Additional Information</h3>
<ul class="list-unstyled">
<li><strong>Mother's Maiden Name:</strong> {{ displayModel.mothers_maiden_name }}</li>
<li><strong>Birth Place:</strong> {{ displayModel.birth_place }}</li>
<li><strong>Multiple Birth:</strong> {{ displayModel.multiple_birth ? 'Yes' : 'No' }}</li>
</ul>
</div>
<!-- Identifiers -->
<div class="col-md-6 mb-4">
<h3 class="h5 mb-3">Identifiers</h3>
<ul class="list-unstyled">
<li *ngIf="displayModel.mrn"><strong>Medical Record Number:</strong> <code class="ml-2 text-muted">{{ displayModel.mrn }}</code></li>
<li *ngIf="displayModel.ssn"><strong>SSN:</strong> <code class="ml-2 text-muted">{{ displayModel.ssn }}</code></li>
</ul>
<ng-container *ngIf="displayModel.identifiers && displayModel.identifiers.length > 0">
<h4 class="h6 mt-3">Other Identifiers:</h4>
<ul class="list-unstyled ml-3">
<li *ngFor="let identifier of displayModel.identifiers">
<strong>{{ identifier.type }}:</strong> <code class="ml-2 text-muted">{{ identifier.value }}</code>
</li>
</ul>
</ng-container>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
.bg-fasten-purple {
background-color: #5b47fb;
}
.patient-card {
border: 1px solid #ccc;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
h2 {
margin-top: 0;
margin-bottom: 15px;
font-size: 1.5rem;
}
.patient-info {
p {
margin: 5px 0;
}
strong {
font-weight: bold;
margin-right: 5px;
}
}
}

View File

@ -0,0 +1,28 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { PatientModel } from 'src/lib/models/resources/patient-model';
import { PatientComponent } from './patient.component';
describe('PatientComponent', () => {
let component: PatientComponent;
let fixture: ComponentFixture<PatientComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ PatientComponent, NgbCollapseModule, RouterTestingModule ]
})
.compileComponents();
fixture = TestBed.createComponent(PatientComponent);
component = fixture.componentInstance;
component.displayModel = new PatientModel({});
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,31 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { PatientModel } from '../../../../../lib/models/resources/patient-model';
import { BadgeComponent } from '../../common/badge/badge.component';
import { TableComponent } from '../../common/table/table.component';
import { FhirCardComponentInterface } from '../../fhir-card/fhir-card-component-interface';
@Component({
standalone: true,
imports: [NgbCollapseModule, CommonModule, BadgeComponent, TableComponent, RouterModule],
selector: 'fhir-patient',
templateUrl: './patient.component.html',
styleUrls: ['./patient.component.scss']
})
export class PatientComponent implements OnInit, FhirCardComponentInterface {
@Input() displayModel: PatientModel;
@Input() showDetails: boolean = true;
@Input() isCollapsed: boolean = false;
constructor(public changeRef: ChangeDetectorRef, public router: Router) { }
ngOnInit(): void {
}
markForCheck(){
this.changeRef.markForCheck()
}
}

View File

@ -1,7 +1,7 @@
<div>
<ngx-datatable
#table
class="bootstrap"
class="bootstrap fhir-resource-datatable"
[columns]="columns"
[columnMode]="ColumnMode.force"
[headerHeight]="50"

View File

@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {attributeXTime} from './utils';
import {GenericColumnDefn, DatatableGenericResourceComponent} from './datatable-generic-resource.component';
@Component({
selector: 'fhir-datatable-patient',
templateUrl: './datatable-generic-resource.component.html',
styleUrls: ['./datatable-generic-resource.component.scss']
})
export class DatatablePatientComponent extends DatatableGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Name', versions: '*', format: 'humanName', getter: p => p.name?.[0] },
{ title: 'DOB', versions: '*', getter: p => p.birthDate, format: 'date' },
]
}

View File

@ -14,6 +14,19 @@ export function getPath(obj, path = ""): string {
}
export const FORMATTERS = {
age: (patientDOB: number|string): number => {
if (patientDOB == null) { return NaN; }
const dob = typeof patientDOB === 'string' ? new Date(patientDOB) : new Date(patientDOB);
const today = new Date();
let age = today.getFullYear() - dob.getFullYear();
const monthDiff = today.getMonth() - dob.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {
age--;
}
return age;
},
date: (str) => str ? moment(str).format('YYYY-MM-DD') : '',
time: (str) => str ? moment(str).format('HH:mm') : '',
dateTime: (str) => str ? moment(str).format('YYYY-MM-DD - h:mm a') : '',
@ -24,15 +37,13 @@ export const FORMATTERS = {
if(codeableConcept.text) return codeableConcept.text
return codeableConcept.coding && codeableConcept.coding[0] ? `${codeableConcept.coding[0].code}: ${codeableConcept.coding[0].display ? codeableConcept.coding[0].display : ''}` : ''
},
address: (address) => {
if(!address) return ''
address: (address): Array<string> => {
if(!address) return []
var addressParts = []
if(address.line) addressParts.push(address.line.join(', '))
if(address.city) addressParts.push(address.city)
if(address.state) addressParts.push(address.state)
if(address.postalCode) addressParts.push(address.postalCode)
if(address.line) addressParts.push(...address.line)
addressParts.push(`${address.city}, ${address.state} ${address.postalCode}`)
if(address.country) addressParts.push(address.country)
return addressParts.join(', ')
return addressParts
},
humanName: (humanName) => {
if(!humanName) return ''

View File

@ -25,7 +25,7 @@ import {DatatableMedicationRequestComponent} from './datatable-generic-resource/
import {DatatableNutritionOrderComponent} from './datatable-generic-resource/datatable-nutrition-order.component';
import {DatatableObservationComponent} from './datatable-generic-resource/datatable-observation.component';
import {DatatableOrganizationComponent} from './datatable-generic-resource/datatable-organization.component';
import {ListPatientComponent} from './list-patient/list-patient.component';
import {DatatablePatientComponent} from './datatable-generic-resource/datatable-patient.component';
import {DatatablePractitionerComponent} from './datatable-generic-resource/datatable-practitioner.component';
import {DatatableProcedureComponent} from './datatable-generic-resource/datatable-procedure.component';
import {DatatableServiceRequestComponent} from './datatable-generic-resource/datatable-service-request.component';
@ -65,7 +65,7 @@ import {NgxDatatableModule} from '@swimlane/ngx-datatable';
DatatableNutritionOrderComponent,
DatatableObservationComponent,
DatatableOrganizationComponent,
ListPatientComponent,
DatatablePatientComponent,
DatatablePractitionerComponent,
DatatableProcedureComponent,
DatatableServiceRequestComponent,
@ -99,7 +99,7 @@ import {NgxDatatableModule} from '@swimlane/ngx-datatable';
DatatableNutritionOrderComponent,
DatatableObservationComponent,
DatatableOrganizationComponent,
ListPatientComponent,
DatatablePatientComponent,
DatatablePractitionerComponent,
DatatableProcedureComponent,
DatatableServiceRequestComponent,

View File

@ -27,6 +27,7 @@ import {DatatableMedicationRequestComponent} from '../datatable-generic-resource
import {DatatableNutritionOrderComponent} from '../datatable-generic-resource/datatable-nutrition-order.component';
import {DatatableObservationComponent} from '../datatable-generic-resource/datatable-observation.component';
import {DatatableOrganizationComponent} from '../datatable-generic-resource/datatable-organization.component';
import {DatatablePatientComponent} from '../datatable-generic-resource/datatable-patient.component';
import {DatatablePractitionerComponent} from '../datatable-generic-resource/datatable-practitioner.component';
import {DatatableProcedureComponent} from '../datatable-generic-resource/datatable-procedure.component';
import {DatatableServiceRequestComponent} from '../datatable-generic-resource/datatable-service-request.component';
@ -162,6 +163,9 @@ export class FhirDatatableComponent implements OnInit, OnChanges {
case "Organization": {
return DatatableOrganizationComponent;
}
case "Patient": {
return DatatablePatientComponent;
}
case "Practitioner": {
return DatatablePractitionerComponent;
}

View File

@ -1,4 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { HTTP_CLIENT_TOKEN } from '../../dependency-injection';
import { FormRequestHealthSystemComponent } from './form-request-health-system.component';
@ -8,7 +13,15 @@ describe('FormRequestHealthSystemComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FormRequestHealthSystemComponent ]
declarations: [ FormRequestHealthSystemComponent ],
imports: [HttpClientTestingModule, FormsModule],
providers: [
NgbActiveModal,
{
provide: HTTP_CLIENT_TOKEN,
useClass: HttpClient,
},
]
})
.compileComponents();

View File

@ -1,9 +1,6 @@
import { Component, OnInit } from '@angular/core';
import {NgbActiveModal, NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {SupportRequest} from '../../models/fasten/support-request';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { FormRequestHealthSystem } from '../../models/fasten/form-request-health-system';
import {environment} from '../../../environments/environment';
import {versionInfo} from '../../../environments/versions';
import { FastenApiService } from '../../services/fasten-api.service';
@Component({

View File

@ -264,6 +264,9 @@ app-medical-sources-filter > .az-content-left-components:hover{
background-color: $indigo !important;
color: #fff;
}
.fhir-resource-datatable .datatable-body-row {
cursor: pointer;
}
// Callouts

View File

@ -1,7 +1,148 @@
import { PatientModel } from './patient-model';
describe('PatientModel', () => {
let patientModel: PatientModel;
let mockFhirResource: any;
beforeEach(() => {
mockFhirResource = {
id: '123',
name: [{ given: ['John'], family: 'Doe' }],
birthDate: '1990-01-01',
gender: 'male',
telecom: [
{ system: 'phone', value: '123-456-7890', use: 'home' },
{ system: 'email', value: 'john@example.com', use: 'work' }
],
communication: [
{ language: { coding: [{ system: 'urn:ietf:bcp:47', code: 'en', display: 'English' }] } }
],
maritalStatus: { text: 'Married' },
extension: [
{
url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex',
valueCode: 'M'
},
{
url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race',
extension: [{ url: 'text', valueString: 'White' }]
},
{
url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity',
extension: [{ url: 'text', valueString: 'Not Hispanic or Latino' }]
},
{
url: 'http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName',
valueString: 'Smith'
},
{
url: 'http://hl7.org/fhir/StructureDefinition/patient-birthPlace',
valueAddress: { city: 'New York', state: 'NY', country: 'USA' }
}
],
identifier: [
{ system: 'http://hl7.org/fhir/sid/us-ssn', value: '123-45-6789' },
{ type: { coding: [{ code: 'MR' }] }, value: 'MRN12345' },
{ type: { text: 'Driver\'s License', coding: [{ display: 'DL' }] }, system: 'urn:oid:2.16.840.1.113883.4.3.25', value: 'S99969890' }
]
};
patientModel = new PatientModel(mockFhirResource);
});
it('should create an instance', () => {
expect(new PatientModel({})).toBeTruthy();
});
it('should return the correct id', () => {
expect(patientModel.id).toBe('123');
});
it('should return the formatted name', () => {
expect(patientModel.patient_name).toBe('John Doe');
});
it('should return the correct birth date', () => {
expect(patientModel.patient_birthdate).toBe('1990-01-01');
});
// it('should return the correct age', () => {
// const currentDate = new Date('2022-02-01');
// jasmine.clock().install();
// jasmine.clock().mockDate(currentDate);
// expect(FORMATTERS.age('1990-01-01')).toBe(32);
// expect(patientModel.patient_birthdate).toBe('1990-01-01');
// expect(patientModel.patient_age).toBe(32);
// jasmine.clock().uninstall();
// });
it('should return the correct gender', () => {
expect(patientModel.patient_gender).toBe('male');
});
it('should return the correct telecom information', () => {
expect(patientModel.patient_phones).toEqual([
{ system: 'phone', value: '123-456-7890', use: 'home' },
{ system: 'email', value: 'john@example.com', use: 'work' }
]);
});
it('should return the correct language information', () => {
expect(patientModel.communication_languages).toEqual([
{ system: 'urn:ietf:bcp:47', code: 'en', display: 'English' }
]);
});
it('should return the correct birth sex', () => {
expect(patientModel.birth_sex).toBe('M');
});
it('should return the correct marital status', () => {
expect(patientModel.marital_status).toBe('Married');
});
it('should return the correct race', () => {
expect(patientModel.race).toBe('White');
});
it('should return the correct ethnicity', () => {
expect(patientModel.ethnicity).toBe('Not Hispanic or Latino');
});
it('should return the correct mother\'s maiden name', () => {
expect(patientModel.mothers_maiden_name).toBe('Smith');
});
it('should return the correct birth place', () => {
expect(patientModel.birth_place).toBe('New York, NY, USA');
});
it('should correctly parse other identifiers', () => {
expect(patientModel.identifiers).toEqual([
{ type: 'Driver\'s License', system: 'urn:oid:2.16.840.1.113883.4.3.25', value: 'S99969890' }
]);
});
// it('should return the correct extensions', () => {
// const extensions = patientModel.getExtensions(mockFhirResource);
// expect(extensions).toContainEqual({ url: 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex', value: 'M' });
// expect(extensions).toContainEqual({ url: 'http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName', value: 'Smith' });
// });
it('should return the correct SSN', () => {
expect(patientModel.ssn).toBe('123-45-6789');
});
it('should return the correct MRN', () => {
const mockResourceWithMRN = {
...mockFhirResource,
extension: [
...mockFhirResource.extension,
{ url: 'http://hl7.org/fhir/StructureDefinition/patient-mrn', valueString: 'MRN54321' }
]
};
expect(patientModel.getMRN(mockResourceWithMRN)).toBe('MRN54321');
});
});

View File

@ -1,8 +1,6 @@
import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
import {CodingModel} from '../datatypes/coding-model';
import { FORMATTERS } from 'src/app/components/fhir-datatable/datatable-generic-resource/utils';
import { fhirVersions, ResourceType } from '../constants';
import { FastenDisplayModel } from '../fasten/fasten-display-model';
import { FastenOptions } from '../fasten/fasten-options';
@ -11,54 +9,153 @@ export class PatientModel extends FastenDisplayModel {
id: string|undefined
patient_name: string|undefined
patient_birthdate: string|undefined
patient_age: number|undefined
patient_gender: string|undefined
patient_contact: string|undefined
patient_address: string|undefined
patient_phones: string|undefined
communication_language: string|undefined
patient_address: Array<string>|undefined
patient_phones: Array<any>|undefined
communication_languages: Array<any>|undefined
has_communication_language: boolean|undefined
has_patient_phones: boolean|undefined
active: string|undefined
active_status: string|undefined
is_deceased: boolean|undefined
deceased_date: string|undefined
birth_sex: string|undefined
marital_status: string|undefined
race: string|undefined
ethnicity: string|undefined
mothers_maiden_name: string|undefined
birth_place: string|undefined
multiple_birth: boolean|undefined
identifiers: Array<any>|undefined
extensions: Array<any>|undefined
ssn: string|undefined
mrn: string|undefined
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
this.source_resource_type = ResourceType.Patient
this.id = this.getId(fhirResource);
this.patient_name = this.getNames(fhirResource);
this.patient_birthdate = this.getBirthDate(fhirResource);
this.patient_gender = this.getGender(fhirResource);
this.id = _.get(fhirResource, 'id');
this.patient_name = FORMATTERS.humanName(_.get(fhirResource, 'name.0', null));
this.patient_birthdate = _.get(fhirResource, 'birthDate');
this.patient_age = FORMATTERS.age(this.patient_birthdate)
this.patient_gender = _.get(fhirResource, 'gender');
this.patient_contact = _.get(fhirResource, 'contact[0]');
this.patient_address = _.get(fhirResource, 'address[0]');
this.patient_phones = _.get(fhirResource, 'telecom', []).filter(
(telecom: any) => telecom.system === 'phone',
);
let communicationLanguage = _.get(fhirResource, 'communication', [])
.filter((item: any) => Boolean(_.get(item, 'language.coding', null)))
.map((item: any) => item.language.coding);
this.communication_language = _.get(communicationLanguage, '0', []);
this.has_communication_language = !_.isEmpty(communicationLanguage);
this.patient_address = FORMATTERS.address(_.get(fhirResource, 'address[0]'));
this.patient_phones = this.getTelecom(fhirResource);
this.communication_languages = this.getCommunicationLanguages(fhirResource);
this.has_communication_language = !_.isEmpty(this.communication_languages);
this.has_patient_phones = !_.isEmpty(this.patient_phones);
this.active = _.get(fhirResource, 'active', false);
this.active_status = this.active ? 'active' : 'inactive';
let deceasedBoolean = _.get(fhirResource, 'deceasedBoolean', false);
this.deceased_date = _.get(fhirResource, 'deceasedDateTime');
this.is_deceased = deceasedBoolean || this.deceased_date;
this.birth_sex = this.getBirthSex(fhirResource);
this.marital_status = _.get(fhirResource, 'maritalStatus.text');
this.race = this.getRace(fhirResource);
this.ethnicity = this.getEthnicity(fhirResource);
this.mothers_maiden_name = this.getMothersMaidenName(fhirResource);
this.birth_place = this.getBirthPlace(fhirResource);
this.multiple_birth = _.get(fhirResource, 'multipleBirthBoolean', false);
this.identifiers = [];
this.parseIdentifiers(fhirResource);
this.extensions = this.getExtensions(fhirResource);
this.ssn = this.ssn || this.getSSN(fhirResource);
this.mrn = this.mrn || this.getMRN(fhirResource);
}
getId(fhirResource: any) {
return _.get(fhirResource, 'id');
getTelecom(fhirResource: any) {
return _.get(fhirResource, 'telecom', []).map((telecom: any) => ({
system: telecom.system,
value: telecom.value,
use: telecom.use
}));
}
getNames(fhirResource: any) {
return _.get(fhirResource, 'name.0', null);
getCommunicationLanguages(fhirResource: any) {
return _.get(fhirResource, 'communication', [])
.filter((item: any) => Boolean(_.get(item, 'language.coding', null)))
.map((item: any) => item.language.coding[0]);
}
getBirthDate(fhirResource: any) {
return _.get(fhirResource, 'birthDate');
getBirthSex(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"
);
return extension?.valueCode;
}
getGender(fhirResource: any) {
return _.get(fhirResource, 'gender');
getRace(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
);
return extension?.extension?.find((ext: any) => ext.url === "text")?.valueString;
}
getEthnicity(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
);
return extension?.extension?.find((ext: any) => ext.url === "text")?.valueString;
}
getMothersMaidenName(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName"
);
return extension?.valueString;
}
getBirthPlace(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/StructureDefinition/patient-birthPlace"
);
if (extension?.valueAddress) {
const address = extension.valueAddress;
return `${address.city}, ${address.state}, ${address.country}`;
}
return undefined;
}
parseIdentifiers(fhirResource: any) {
const identifiers = _.get(fhirResource, 'identifier', []);
identifiers.forEach((identifier: any) => {
if (identifier.system === "http://hl7.org/fhir/sid/us-ssn") {
this.ssn = identifier.value;
} else if (identifier.type?.coding?.some((coding: any) => coding.code === "MR")) {
this.mrn = identifier.value;
} else if (identifier.type) {
this.identifiers.push({
type: identifier.type.text || identifier.type.coding[0].display,
system: identifier.system,
value: identifier.value
});
}
});
}
getExtensions(fhirResource: any) {
return fhirResource.extension?.map((ext: any) => ({
url: ext.url,
value: ext.valueDecimal || ext.valueString || ext.valueCode || JSON.stringify(ext.extension)
}));
}
getSSN(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/StructureDefinition/us-core-ssn"
);
return extension?.valueString;
}
getMRN(fhirResource: any) {
const extension = fhirResource.extension?.find((ext: any) =>
ext.url === "http://hl7.org/fhir/StructureDefinition/patient-mrn"
);
return extension?.valueString;
}
}