patient resource list and card (#501)
This commit is contained in:
parent
7b7a0055d7
commit
4642223657
|
@ -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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<div>
|
||||
<ngx-datatable
|
||||
#table
|
||||
class="bootstrap"
|
||||
class="bootstrap fhir-resource-datatable"
|
||||
[columns]="columns"
|
||||
[columnMode]="ColumnMode.force"
|
||||
[headerHeight]="50"
|
||||
|
|
|
@ -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' },
|
||||
]
|
||||
}
|
|
@ -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 ''
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue