diff --git a/frontend/src/app/pages/medical-sources/medical-sources.component.ts b/frontend/src/app/pages/medical-sources/medical-sources.component.ts
index 4f0077e0..69f6c11e 100644
--- a/frontend/src/app/pages/medical-sources/medical-sources.component.ts
+++ b/frontend/src/app/pages/medical-sources/medical-sources.component.ts
@@ -1,4 +1,4 @@
-import {Component, OnInit} from '@angular/core';
+import {Component, EventEmitter, OnInit, Optional, Output} from '@angular/core';
import {LighthouseService} from '../../services/lighthouse.service';
import {FastenApiService} from '../../services/fasten-api.service';
import {LighthouseSourceMetadata} from '../../models/lighthouse/lighthouse-source-metadata';
@@ -10,9 +10,15 @@ import {Location} from '@angular/common';
import {ToastService} from '../../services/toast.service';
import {ToastNotification, ToastType} from '../../models/fasten/toast';
import {environment} from '../../../environments/environment';
-import {BehaviorSubject, forkJoin, Subject} from 'rxjs';
-import {LighthouseSourceSearch} from '../../models/lighthouse/lighthouse-source-search';
+import {BehaviorSubject, forkJoin, Observable, Subject} from 'rxjs';
+import {
+ LighthouseSourceSearch,
+ LighthouseSourceSearchAggregation,
+ LighthouseSourceSearchResult
+} from '../../models/lighthouse/lighthouse-source-search';
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';
+import {MedicalSourcesFilter, MedicalSourcesFilterService} from '../../services/medical-sources-filter.service';
+import {FormControl, FormGroup} from '@angular/forms';
// If you dont import this angular will import the wrong "Location"
export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)
@@ -31,78 +37,145 @@ export class MedicalSourcesComponent implements OnInit {
loading: boolean = false
environment_name = environment.environment_name
- status: { [name: string]: string } = {}
- connectedSourceList: SourceListItem[] = [] //source's are populated for this list
- availableSourceList: SourceListItem[] = []
- totalAvailableSourceList: number = 0
uploadedFile: File[] = []
- closeResult = '';
- modalSelectedSourceListItem:SourceListItem = null;
- scrollId: string = ""
- scrollComplete: boolean = false
+ availableSourceList: SourceListItem[] = []
searchTermUpdate = new BehaviorSubject("");
- showHidden: boolean = false
+ status: { [name: string]: string } = {}
+
+ //aggregation/filter data & limits
+ globalLimits: {
+ // aggregations: LighthouseSourceSearchAggregations | undefined,
+ } = {
+ // categories: [],
+ // aggregations: undefined,
+ }
+
+ //limits that are tied to the current result set.
+ resultLimits: {
+ totalItems: number,
+ scrollComplete: boolean,
+ platformTypesBuckets: LighthouseSourceSearchAggregation,
+ categoryBuckets: LighthouseSourceSearchAggregation,
+ } = {
+ totalItems: 0,
+ scrollComplete: false,
+ platformTypesBuckets: undefined,
+ categoryBuckets: undefined
+ }
+
+
+ //source of truth for current state
+ filterForm = this.filterService.filterForm;
constructor(
private lighthouseApi: LighthouseService,
private fastenApi: FastenApiService,
- private modalService: NgbModal,
- private route: ActivatedRoute,
+ private activatedRoute: ActivatedRoute,
private router: Router,
private location: Location,
- private toastService: ToastService
+ private toastService: ToastService,
+ private filterService: MedicalSourcesFilterService,
+
) { }
ngOnInit(): void {
- this.loading = true
- forkJoin([this.lighthouseApi.findLighthouseSources("", "", this.showHidden), this.fastenApi.getSources()]).subscribe(results => {
- this.loading = false
- //handle connected sources sources
- const connectedSources = results[1] as Source[]
- forkJoin(connectedSources.map((source) => this.lighthouseApi.getLighthouseSource(source.source_type))).subscribe((connectedMetadata) => {
- for(const ndx in connectedSources){
- this.connectedSourceList.push({source: connectedSources[ndx], metadata: connectedMetadata[ndx]})
- }
- })
+ //changing the form, should change the URL, BUT NOT do a query
+ this.filterForm.valueChanges.pipe(debounceTime(100)).subscribe(val => {
+ console.log("FILTER FORM CHANGED:", val, this.filterService.toQueryParams())
-
- //handle source metadata map response
- this.populateAvailableSourceList(results[0] as LighthouseSourceSearch)
-
-
- //check if we've just started connecting a "source_type"
- const callbackSourceType = this.route.snapshot.paramMap.get('source_type')
- if(callbackSourceType){
- this.status[callbackSourceType] = "token"
-
- //move this source from available to connected (with a progress bar)
- //remove item from available sources list, add to connected sources.
- let inProgressAvailableIndex = this.availableSourceList.findIndex((item) => item.metadata.source_type == callbackSourceType)
- if(inProgressAvailableIndex > -1){
- let sourcesInProgress = this.availableSourceList.splice(inProgressAvailableIndex, 1);
-
- }
-
- //the structure of "availableSourceList" vs "connectedSourceList" sources is slightly different,
- //connectedSourceList contains a "source" field. The this.fastenApi.createSource() call in the callback function will set it.
- this.lighthouseApi.getLighthouseSource(callbackSourceType)
- .then((metadata) => {
- this.connectedSourceList.push({metadata: metadata})
- return this.callback(callbackSourceType)
- })
- .then(console.log)
- }
-
- }, err => {
- this.loading = false
+ // change the browser url whenever the filter is updated.
+ this.updateBrowserUrl(this.filterService.toQueryParams())
})
+ //TODO: handle Callbacks from the source connect window
+ const callbackSourceType = this.activatedRoute.snapshot.paramMap.get('source_type')
+ if(callbackSourceType){
+ console.error("TODO! handle callback redirect from source")
+ } else {
+ //we're not in a callback redirect, lets load the sources
+ if(this.activatedRoute.snapshot.queryParams['query']){
+ this.searchTermUpdate.next(this.activatedRoute.snapshot.queryParams['query'])
+ }
- //register a callback for when the search term changes
+
+ //changing the route should trigger a query
+ this.activatedRoute.queryParams
+ .subscribe(params => {
+ console.log("QUERY PARAMS CHANGED ON ROUTE", params); // {order: "popular"}
+ var updatedForm = this.filterService.parseQueryParams(params);
+
+ //this is a "breaking change" to the filter values, causing a reset and a new query
+ this.availableSourceList = []
+ this.resultLimits.totalItems = 0
+ this.resultLimits.scrollComplete = false
+ // this.filterService.filterForm.setControl("categories", this.{: {}}, { emitEvent: false})
+
+ //update the form with data from route (don't emit a new patch event), then submit query
+ var searchObservable = this.querySources(this.filterService.toMedicalSourcesFilter(updatedForm));
+ searchObservable.subscribe(null, null, () => {
+ this.filterForm.patchValue(updatedForm, { emitEvent: false});
+ })
+ });
+ }
+
+
+
+
+ /// OLD CODE - Should be refactored
+
+
+
+
+ // this.loading = true
+ // forkJoin([this.lighthouseApi.findLighthouseSources("", "", this.showHidden), this.fastenApi.getSources()]).subscribe(results => {
+ // this.loading = false
+ //
+ // //handle connected sources sources
+ // const connectedSources = results[1] as Source[]
+ // forkJoin(connectedSources.map((source) => this.lighthouseApi.getLighthouseSource(source.source_type))).subscribe((connectedMetadata) => {
+ // for(const ndx in connectedSources){
+ // this.connectedSourceList.push({source: connectedSources[ndx], metadata: connectedMetadata[ndx]})
+ // }
+ // })
+ //
+ //
+ // //handle source metadata map response
+ // this.populateAvailableSourceList(results[0] as LighthouseSourceSearch)
+ //
+ //
+ // //check if we've just started connecting a "source_type"
+ // const callbackSourceType = this.route.snapshot.paramMap.get('source_type')
+ // if(callbackSourceType){
+ // this.status[callbackSourceType] = "token"
+ //
+ // //move this source from available to connected (with a progress bar)
+ // //remove item from available sources list, add to connected sources.
+ // let inProgressAvailableIndex = this.availableSourceList.findIndex((item) => item.metadata.source_type == callbackSourceType)
+ // if(inProgressAvailableIndex > -1){
+ // let sourcesInProgress = this.availableSourceList.splice(inProgressAvailableIndex, 1);
+ //
+ // }
+ //
+ // //the structure of "availableSourceList" vs "connectedSourceList" sources is slightly different,
+ // //connectedSourceList contains a "source" field. The this.fastenApi.createSource() call in the callback function will set it.
+ // this.lighthouseApi.getLighthouseSource(callbackSourceType)
+ // .then((metadata) => {
+ // this.connectedSourceList.push({metadata: metadata})
+ // return this.callback(callbackSourceType)
+ // })
+ // .then(console.log)
+ // }
+ //
+ // }, err => {
+ // this.loading = false
+ // })
+ //
+ //
+ //register a callback for when the search box content changes
this.searchTermUpdate
.pipe(
debounceTime(200),
@@ -110,215 +183,293 @@ export class MedicalSourcesComponent implements OnInit {
)
.subscribe(value => {
console.log("search term changed:", value)
-
- //reset available sources
- this.availableSourceList = []
- this.scrollId = ""
- this.scrollComplete = false
- this.totalAvailableSourceList = 0
-
- this.lighthouseApi.findLighthouseSources(value, this.scrollId, this.showHidden)
- .subscribe((results) => {
- this.populateAvailableSourceList(results)
- })
+ let currentQuery = this.filterService.filterForm.value.query || ""
+ if(value != null && currentQuery != value){
+ this.filterService.filterForm.patchValue({query: value})
+ }
});
}
- private populateAvailableSourceList(results: LighthouseSourceSearch): void {
- this.totalAvailableSourceList = results.hits.total.value
- if(results.hits.hits.length == 0){
- this.scrollComplete = true
- console.log("scroll complete")
+ updateBrowserUrl(queryParams: {[name: string]: string}){
+ console.log("update the browser url with query params data", queryParams)
+ this.router.navigate(['/sources'], { queryParams: queryParams })
+ }
+
+ private querySources(filter?: MedicalSourcesFilter): Observable {
+ if(this.loading){
return
}
- this.scrollId = results._scroll_id
- this.availableSourceList = this.availableSourceList.concat(results.hits.hits.map((result) => {
- return {metadata: result._source}
- }).filter((item) => {
- return !this.connectedSourceList.find((connectedItem) => connectedItem.metadata.source_type == item.metadata.source_type)
- }))
- }
+ //TODO: pass filter to function.
+ // this.location.replaceState('/dashboard','', this.filter)
- public onScroll(): void {
- if(this.scrollComplete){
- return
+ if(!filter){
+ filter = this.filterService.toMedicalSourcesFilter(this.filterForm.value)
+ console.log("querySources() - no filter provided, using current form value", filter)
}
- this.lighthouseApi.findLighthouseSources(this.searchTermUpdate.getValue(), this.scrollId, this.showHidden)
- .subscribe((results) => {
- this.populateAvailableSourceList(results)
- })
- }
- /**
- * after pressing the logo (connectHandler button), this function will generate an authorize url for this source, and redirec the user.
- * @param $event
- * @param sourceType
- */
- public connectHandler($event: MouseEvent, sourceType: string):void {
- ($event.currentTarget as HTMLButtonElement).disabled = true;
- this.status[sourceType] = "authorize"
+ filter.fields = ["*"];
+ this.loading = true
+ var searchObservable = this.lighthouseApi.searchLighthouseSources(filter);
+ searchObservable.subscribe(wrapper => {
+ console.log("search sources", wrapper);
+ // this.searchResults = wrapper.hits.hits;
+ this.resultLimits.totalItems = wrapper.hits.total.value;
- this.lighthouseApi.getLighthouseSource(sourceType)
- .then(async (sourceMetadata: LighthouseSourceMetadata) => {
- console.log(sourceMetadata);
- let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceType, sourceMetadata)
+ this.availableSourceList = this.availableSourceList.concat(wrapper.hits.hits.map((result) => {
+ return {metadata: result._source}
+ }))
- console.log('authorize url:', authorizationUrl.toString());
- // redirect to lighthouse with uri's
- this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceType, sourceMetadata.redirect_uri)
+ //change the current Page (but don't cause a new query)
+ if(wrapper.hits.hits.length == 0){
+ console.log("SCROLL_COMPLETE!@@@@@@@@")
+ this.resultLimits.scrollComplete = true;
+ } else {
+ console.log("SETTING NEXT SORT KEY:", wrapper.hits.hits[wrapper.hits.hits.length - 1].sort.join(','))
+ this.filterService.filterForm.patchValue({searchAfter: wrapper.hits.hits[wrapper.hits.hits.length - 1].sort.join(",")}, {emitEvent: false})
+ }
- });
- }
+ // .filter((item) => {
+ // return !this.connectedSourceList.find((connectedItem) => connectedItem.metadata.source_type == item.metadata.source_type)
+ // }))
- /**
- * if the user is redirected to this page from the lighthouse, we'll need to process the "code" to retrieve the access token & refresh token.
- * @param sourceType
- */
- public async callback(sourceType: string) {
- //get the source metadata again
- await this.lighthouseApi.getLighthouseSource(sourceType)
- .then(async (sourceMetadata: LighthouseSourceMetadata) => {
- //get required parameters from the URI and local storage
- const callbackUrlParts = new URL(window.location.href)
- const fragmentParams = new URLSearchParams(callbackUrlParts.hash.substring(1))
- const callbackCode = callbackUrlParts.searchParams.get("code") || fragmentParams.get("code")
- const callbackState = callbackUrlParts.searchParams.get("state") || fragmentParams.get("state")
- const callbackError = callbackUrlParts.searchParams.get("error") || fragmentParams.get("error")
- const callbackErrorDescription = callbackUrlParts.searchParams.get("error_description") || fragmentParams.get("error_description")
+ this.resultLimits.platformTypesBuckets = wrapper.aggregations.by_platform_type;
+ this.resultLimits.categoryBuckets = wrapper.aggregations.by_category;
- //reset the url, removing the params and fragment from the current url.
- const urlTree = this.router.createUrlTree(["/sources"],{
- relativeTo: this.route,
- });
- this.location.replaceState(urlTree.toString());
- const expectedSourceStateInfo = JSON.parse(localStorage.getItem(callbackState))
- localStorage.removeItem(callbackState)
- if(callbackError && !callbackCode){
- //TOOD: print this message in the UI
- let errMsg = "an error occurred while authenticating to this source. Please try again later"
- console.error(errMsg, callbackErrorDescription)
- throw new Error(errMsg)
- }
-
- console.log("callback code:", callbackCode)
- this.status[sourceType] = "token"
-
- let payload: any
- payload = await this.lighthouseApi.swapOauthToken(sourceType, sourceMetadata,expectedSourceStateInfo, callbackCode)
-
- if(!payload.access_token || payload.error){
- //if the access token is not set, then something is wrong,
- let errMsg = payload.error || "unable to retrieve access_token"
- console.error(errMsg)
- throw new Error(errMsg)
- }
-
- //If payload.patient is not set, make sure we extract the patient ID from the id_token or make an introspection req
- if(!payload.patient && payload.id_token){
- //
- console.log("NO PATIENT ID present, decoding jwt to extract patient")
- //const introspectionResp = await Oauth.introspectionRequest(as, client, payload.access_token)
- //console.log(introspectionResp)
- let decodedIdToken = this.jwtDecode(payload.id_token)
- //nextGen uses fhirUser instead of profile.
- payload.patient = decodedIdToken["profile"] || decodedIdToken["fhirUser"]
-
- if(payload.patient){
- payload.patient = payload.patient.replace(/^(Patient\/)/,'')
+ var currentCategories = this.filterForm.get('categories').value;
+ this.resultLimits.categoryBuckets.buckets.forEach((bucketData) => {
+ if(!currentCategories.hasOwnProperty(bucketData.key)){
+ (this.filterForm.get('categories') as FormGroup).addControl(bucketData.key, new FormControl(false))
}
-
- }
-
-
-
- //Create FHIR Client
-
- const dbSourceCredential = new Source({
- source_type: sourceType,
-
- authorization_endpoint: sourceMetadata.authorization_endpoint,
- token_endpoint: sourceMetadata.token_endpoint,
- introspection_endpoint: sourceMetadata.introspection_endpoint,
- userinfo_endpoint: sourceMetadata.userinfo_endpoint,
- api_endpoint_base_url: sourceMetadata.api_endpoint_base_url,
- client_id: sourceMetadata.client_id,
- redirect_uri: sourceMetadata.redirect_uri,
- scopes_supported: sourceMetadata.scopes_supported,
- issuer: sourceMetadata.issuer,
- grant_types_supported: sourceMetadata.grant_types_supported,
- response_types_supported: sourceMetadata.response_types_supported,
- aud: sourceMetadata.aud,
- code_challenge_methods_supported: sourceMetadata.code_challenge_methods_supported,
- confidential: sourceMetadata.confidential,
- cors_relay_required: sourceMetadata.cors_relay_required,
-
- patient: payload.patient,
- access_token: payload.access_token,
- refresh_token: payload.refresh_token,
- id_token: payload.id_token,
-
- // @ts-ignore - in some cases the getAccessTokenExpiration is a string, which cases failures to store Source in db.
- expires_at: parseInt(this.getAccessTokenExpiration(payload)),
})
+ //
+ // this.resultLimits.categoryBuckets.forEach((bucketData) => {
+ // if(!this.globalLimits.categories.some((category) => { return category.id === bucketData.key})){
+ // this.globalLimits.categories.push({
+ // id: bucketData.key,
+ // name: bucketData.key,
+ // group: 'custom'
+ // })
+ // }
+ // })
- this.fastenApi.createSource(dbSourceCredential)
- .subscribe((resp) => {
- // const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage
- delete this.status[sourceType]
- // window.location.reload();
- // this.connectedSourceList.
-
- //find the index of the "inprogress" source in the connected List, and then add this source to its source metadata.
- let foundSource = this.connectedSourceList.findIndex((item) => item.metadata.source_type == sourceType)
- this.connectedSourceList[foundSource].source = resp.source
-
- console.log("source sync-all response:", resp.summary)
-
- const toastNotification = new ToastNotification()
- toastNotification.type = ToastType.Success
- toastNotification.message = `Successfully connected ${sourceType}`
-
- // const upsertSummary = sourceSyncMessage.response as UpsertSummary
- // if(upsertSummary && upsertSummary.totalResources != upsertSummary.updatedResources.length){
- // toastNotification.message += `\n (total: ${upsertSummary.totalResources}, updated: ${upsertSummary.updatedResources.length})`
- // } else if(upsertSummary){
- // toastNotification.message += `\n (total: ${upsertSummary.totalResources})`
- // }
-
- this.toastService.show(toastNotification)
- },
- (err) => {
- delete this.status[sourceType]
- // window.location.reload();
-
- const toastNotification = new ToastNotification()
- toastNotification.type = ToastType.Error
- toastNotification.message = `An error occurred while accessing ${sourceType}: ${err}`
- toastNotification.autohide = false
- this.toastService.show(toastNotification)
- console.error(err)
- });
- })
- .catch((err) => {
- delete this.status[sourceType]
- // window.location.reload();
-
- const toastNotification = new ToastNotification()
- toastNotification.type = ToastType.Error
- toastNotification.message = `An error occurred while accessing ${sourceType}: ${err}`
- toastNotification.autohide = false
- this.toastService.show(toastNotification)
- console.error(err)
- })
+ // const fileTypes = this.filterForm.get('fileTypes');
+ // fileTypes.forEach((option: any) => {
+ // checkboxes.addControl(option.title, new FormControl(true));
+ // });
+ this.loading = false
+ },
+ error => {
+ this.loading = false
+ console.error("sources FAILED", error)
+ },
+ () => {
+ this.loading = false
+ console.log("sources finished")
+ }
+ );
+ return searchObservable;
}
+ //OLD FUNCTIONS
+ //
+ //
+ // private populateAvailableSourceList(results: LighthouseSourceSearch): void {
+ // console.log("AGGREGATIONS!!!!!", results.aggregations)
+ // this.totalAvailableSourceList = results.hits.total.value
+ // if(results.hits.hits.length == 0){
+ // this.scrollComplete = true
+ // console.log("scroll complete")
+ // return
+ // }
+ // this.scrollId = results._scroll_id
+ // this.availableSourceList = this.availableSourceList.concat(results.hits.hits.map((result) => {
+ // return {metadata: result._source}
+ // }).filter((item) => {
+ // return !this.connectedSourceList.find((connectedItem) => connectedItem.metadata.source_type == item.metadata.source_type)
+ // }))
+ // }
+ //
+ public onScroll(): void {
+ console.log("TODO: SCROLL, TRIGGER update")
+ this.querySources()
+ }
+
+ // /**
+ // * after pressing the logo (connectHandler button), this function will generate an authorize url for this source, and redirec the user.
+ // * @param $event
+ // * @param sourceType
+ // */
+ public connectHandler($event: MouseEvent, sourceType: string):void {
+ console.log("TODO: connect Handler")
+ // ($event.currentTarget as HTMLButtonElement).disabled = true;
+ // this.status[sourceType] = "authorize"
+ //
+ // this.lighthouseApi.getLighthouseSource(sourceType)
+ // .then(async (sourceMetadata: LighthouseSourceMetadata) => {
+ // console.log(sourceMetadata);
+ // let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceType, sourceMetadata)
+ //
+ // console.log('authorize url:', authorizationUrl.toString());
+ // // redirect to lighthouse with uri's
+ // this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceType, sourceMetadata.redirect_uri)
+ //
+ // });
+ }
+ //
+ // /**
+ // * if the user is redirected to this page from the lighthouse, we'll need to process the "code" to retrieve the access token & refresh token.
+ // * @param sourceType
+ // */
+ // public async callback(sourceType: string) {
+ //
+ // //get the source metadata again
+ // await this.lighthouseApi.getLighthouseSource(sourceType)
+ // .then(async (sourceMetadata: LighthouseSourceMetadata) => {
+ //
+ // //get required parameters from the URI and local storage
+ // const callbackUrlParts = new URL(window.location.href)
+ // const fragmentParams = new URLSearchParams(callbackUrlParts.hash.substring(1))
+ // const callbackCode = callbackUrlParts.searchParams.get("code") || fragmentParams.get("code")
+ // const callbackState = callbackUrlParts.searchParams.get("state") || fragmentParams.get("state")
+ // const callbackError = callbackUrlParts.searchParams.get("error") || fragmentParams.get("error")
+ // const callbackErrorDescription = callbackUrlParts.searchParams.get("error_description") || fragmentParams.get("error_description")
+ //
+ // //reset the url, removing the params and fragment from the current url.
+ // const urlTree = this.router.createUrlTree(["/sources"],{
+ // relativeTo: this.route,
+ // });
+ // this.location.replaceState(urlTree.toString());
+ //
+ // const expectedSourceStateInfo = JSON.parse(localStorage.getItem(callbackState))
+ // localStorage.removeItem(callbackState)
+ //
+ // if(callbackError && !callbackCode){
+ // //TOOD: print this message in the UI
+ // let errMsg = "an error occurred while authenticating to this source. Please try again later"
+ // console.error(errMsg, callbackErrorDescription)
+ // throw new Error(errMsg)
+ // }
+ //
+ // console.log("callback code:", callbackCode)
+ // this.status[sourceType] = "token"
+ //
+ // let payload: any
+ // payload = await this.lighthouseApi.swapOauthToken(sourceType, sourceMetadata,expectedSourceStateInfo, callbackCode)
+ //
+ // if(!payload.access_token || payload.error){
+ // //if the access token is not set, then something is wrong,
+ // let errMsg = payload.error || "unable to retrieve access_token"
+ // console.error(errMsg)
+ // throw new Error(errMsg)
+ // }
+ //
+ // //If payload.patient is not set, make sure we extract the patient ID from the id_token or make an introspection req
+ // if(!payload.patient && payload.id_token){
+ // //
+ // console.log("NO PATIENT ID present, decoding jwt to extract patient")
+ // //const introspectionResp = await Oauth.introspectionRequest(as, client, payload.access_token)
+ // //console.log(introspectionResp)
+ // let decodedIdToken = this.jwtDecode(payload.id_token)
+ // //nextGen uses fhirUser instead of profile.
+ // payload.patient = decodedIdToken["profile"] || decodedIdToken["fhirUser"]
+ //
+ // if(payload.patient){
+ // payload.patient = payload.patient.replace(/^(Patient\/)/,'')
+ // }
+ //
+ // }
+ //
+ //
+ //
+ // //Create FHIR Client
+ //
+ // const dbSourceCredential = new Source({
+ // source_type: sourceType,
+ //
+ // authorization_endpoint: sourceMetadata.authorization_endpoint,
+ // token_endpoint: sourceMetadata.token_endpoint,
+ // introspection_endpoint: sourceMetadata.introspection_endpoint,
+ // userinfo_endpoint: sourceMetadata.userinfo_endpoint,
+ // api_endpoint_base_url: sourceMetadata.api_endpoint_base_url,
+ // client_id: sourceMetadata.client_id,
+ // redirect_uri: sourceMetadata.redirect_uri,
+ // scopes_supported: sourceMetadata.scopes_supported,
+ // issuer: sourceMetadata.issuer,
+ // grant_types_supported: sourceMetadata.grant_types_supported,
+ // response_types_supported: sourceMetadata.response_types_supported,
+ // aud: sourceMetadata.aud,
+ // code_challenge_methods_supported: sourceMetadata.code_challenge_methods_supported,
+ // confidential: sourceMetadata.confidential,
+ // cors_relay_required: sourceMetadata.cors_relay_required,
+ //
+ // patient: payload.patient,
+ // access_token: payload.access_token,
+ // refresh_token: payload.refresh_token,
+ // id_token: payload.id_token,
+ //
+ // // @ts-ignore - in some cases the getAccessTokenExpiration is a string, which cases failures to store Source in db.
+ // expires_at: parseInt(this.getAccessTokenExpiration(payload)),
+ // })
+ //
+ // this.fastenApi.createSource(dbSourceCredential)
+ // .subscribe((resp) => {
+ // // const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage
+ // delete this.status[sourceType]
+ // // window.location.reload();
+ // // this.connectedSourceList.
+ //
+ // //find the index of the "inprogress" source in the connected List, and then add this source to its source metadata.
+ // let foundSource = this.connectedSourceList.findIndex((item) => item.metadata.source_type == sourceType)
+ // this.connectedSourceList[foundSource].source = resp.source
+ //
+ // console.log("source sync-all response:", resp.summary)
+ //
+ // const toastNotification = new ToastNotification()
+ // toastNotification.type = ToastType.Success
+ // toastNotification.message = `Successfully connected ${sourceType}`
+ //
+ // // const upsertSummary = sourceSyncMessage.response as UpsertSummary
+ // // if(upsertSummary && upsertSummary.totalResources != upsertSummary.updatedResources.length){
+ // // toastNotification.message += `\n (total: ${upsertSummary.totalResources}, updated: ${upsertSummary.updatedResources.length})`
+ // // } else if(upsertSummary){
+ // // toastNotification.message += `\n (total: ${upsertSummary.totalResources})`
+ // // }
+ //
+ // this.toastService.show(toastNotification)
+ // },
+ // (err) => {
+ // delete this.status[sourceType]
+ // // window.location.reload();
+ //
+ // const toastNotification = new ToastNotification()
+ // toastNotification.type = ToastType.Error
+ // toastNotification.message = `An error occurred while accessing ${sourceType}: ${err}`
+ // toastNotification.autohide = false
+ // this.toastService.show(toastNotification)
+ // console.error(err)
+ // });
+ // })
+ // .catch((err) => {
+ // delete this.status[sourceType]
+ // // window.location.reload();
+ //
+ // const toastNotification = new ToastNotification()
+ // toastNotification.type = ToastType.Error
+ // toastNotification.message = `An error occurred while accessing ${sourceType}: ${err}`
+ // toastNotification.autohide = false
+ // this.toastService.show(toastNotification)
+ // console.error(err)
+ // })
+ // }
+
+
/**
* this function is used to process manually "uploaded" FHIR bundle files, adding them to the database.
* @param event
@@ -337,52 +488,14 @@ export class MedicalSourcesComponent implements OnInit {
)
}
- public openModal(contentModalRef, sourceListItem: SourceListItem) {
- if(this.status[sourceListItem.metadata.source_type] || !sourceListItem.source){
- //if this source is currently "loading" dont open the modal window
- return
- }
- this.modalSelectedSourceListItem = sourceListItem
- this.modalService.open(contentModalRef, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
- this.modalSelectedSourceListItem = null
- this.closeResult = `Closed with: ${result}`;
- }, (reason) => {
- this.modalSelectedSourceListItem = null
- this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
- });
- }
-
- public sourceSyncHandler(source: Source){
- this.status[source.source_type] = "authorize"
- this.modalService.dismissAll()
-
- this.fastenApi.syncSource(source.id).subscribe(
- (respData) => {
- delete this.status[source.source_type]
- console.log("source sync response:", respData)
- },
- (err) => {
- delete this.status[source.source_type]
- console.log(err)
- }
- )
- }
///////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////
- private getDismissReason(reason: any): string {
- if (reason === ModalDismissReasons.ESC) {
- return 'by pressing ESC';
- } else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
- return 'by clicking on a backdrop';
- } else {
- return `with: ${reason}`;
- }
- }
+
/**
diff --git a/frontend/src/app/services/lighthouse.service.ts b/frontend/src/app/services/lighthouse.service.ts
index 23ae4c9c..2b5224b2 100644
--- a/frontend/src/app/services/lighthouse.service.ts
+++ b/frontend/src/app/services/lighthouse.service.ts
@@ -11,6 +11,7 @@ import {MetadataSource} from '../models/fasten/metadata-source';
import {uuidV4} from '../../lib/utils/uuid';
import {LighthouseSourceSearch} from '../models/lighthouse/lighthouse-source-search';
import {HTTP_CLIENT_TOKEN} from "../dependency-injection";
+import {MedicalSourcesFilter} from './medical-sources-filter.service';
@Injectable({
providedIn: 'root'
@@ -20,19 +21,14 @@ export class LighthouseService {
constructor(@Inject(HTTP_CLIENT_TOKEN) private _httpClient: HttpClient) {
}
- public findLighthouseSources(searchTerm: string, scrollId= "", showHidden = false): Observable {
- const endpointUrl = new URL(`${environment.lighthouse_api_endpoint_base}/list/search`);
- if(showHidden){
- endpointUrl.searchParams.set('show_hidden', 'true');
+ public searchLighthouseSources(filter: MedicalSourcesFilter): Observable {
+ if(filter.searchAfter){
+ filter.searchAfter = (filter.searchAfter as string).split(',')
+ } else {
+ filter.searchAfter = []
}
- if(scrollId){
- endpointUrl.searchParams.set('scroll_id', scrollId);
- }
- if(searchTerm){
- endpointUrl.searchParams.set('query', searchTerm);
- }
-
- return this._httpClient.get(endpointUrl.toString())
+ const endpointUrl = new URL(`${environment.lighthouse_api_endpoint_base}/search`);
+ return this._httpClient.post(endpointUrl.toString(), filter)
.pipe(
map((response: ResponseWrapper) => {
console.log("Metadata RESPONSE", response)
@@ -41,6 +37,28 @@ export class LighthouseService {
);
}
+ //deprecated
+ // public findLighthouseSources(searchTerm: string, scrollId= "", showHidden = false): Observable {
+ // const endpointUrl = new URL(`${environment.lighthouse_api_endpoint_base}/list/search`);
+ // if(showHidden){
+ // endpointUrl.searchParams.set('show_hidden', 'true');
+ // }
+ // if(scrollId){
+ // endpointUrl.searchParams.set('scroll_id', scrollId);
+ // }
+ // if(searchTerm){
+ // endpointUrl.searchParams.set('query', searchTerm);
+ // }
+ //
+ // return this._httpClient.get(endpointUrl.toString())
+ // .pipe(
+ // map((response: ResponseWrapper) => {
+ // console.log("Metadata RESPONSE", response)
+ // return response.data as LighthouseSourceSearch
+ // })
+ // );
+ // }
+
public getLighthouseSourceMetadataMap(showHidden = false): Observable<{[name: string]: MetadataSource}> {
const endpointUrl = new URL(`${environment.lighthouse_api_endpoint_base}/list`);
if(showHidden){
@@ -209,881 +227,5 @@ export class LighthouseService {
})
return parts.join(separator);
}
-
-
- public categoryCodeLookup(code: string): string {
- return SOURCE_CATEGORY_CODE_LOOKUP[code];
- }
}
-
-const SOURCE_CATEGORY_CODE_LOOKUP = {
- "101200000X": "Drama Therapist",
- "101Y00000X": "Counselor",
- "101YA0400X": "Addiction (Substance Use Disorder) Counselor",
- "101YM0800X": "Mental Health Counselor",
- "101YP1600X": "Pastoral Counselor",
- "101YP2500X": "Professional Counselor",
- "101YS0200X": "School Counselor",
- "102L00000X": "Psychoanalyst",
- "102X00000X": "Poetry Therapist",
- "103G00000X": "Clinical Neuropsychologist",
- "103GC0700X": "Deactivated - Clinical Neuropsychologist",
- "103K00000X": "Behavioral Analyst",
- "103T00000X": "Psychologist",
- "103TA0400X": "Addiction (Substance Use Disorder) Psychologist",
- "103TA0700X": "Adult Development & Aging Psychologist",
- "103TB0200X": "Cognitive & Behavioral Psychologist",
- "103TC0700X": "Clinical Psychologist",
- "103TC1900X": "Counseling Psychologist",
- "103TC2200X": "Clinical Child & Adolescent Psychologist",
- "103TE1000X": "Deactivated - Psychologist",
- "103TE1100X": "Exercise & Sports Psychologist",
- "103TF0000X": "Family Psychologist",
- "103TF0200X": "Forensic Psychologist",
- "103TH0004X": "Health Psychologist",
- "103TH0100X": "Health Service Psychologist",
- "103TM1700X": "Deactivated - Psychologist Men & Masculinity",
- "103TM1800X": "Intellectual & Developmental Disabilities Psychologist",
- "103TP0016X": "Prescribing (Medical) Psychologist",
- "103TP0814X": "Psychoanalysis Psychologist",
- "103TP2700X": "Deactivated - Psychologist Psychotherapy",
- "103TP2701X": "Group Psychotherapy Psychologist",
- "103TR0400X": "Rehabilitation Psychologist",
- "103TS0200X": "School Psychologist",
- "103TW0100X": "Deactivated - Psychotherapy Women",
- "104100000X": "Social Worker",
- "1041C0700X": "Clinical Social Worker",
- "1041S0200X": "School Social Worker",
- "106E00000X": "Assistant Behavior Analyst",
- "106H00000X": "Marriage & Family Therapist",
- "106S00000X": "Behavior Technician",
- "111N00000X": "Chiropractor",
- "111NI0013X": "Independent Medical Examiner Chiropractor",
- "111NI0900X": "Internist Chiropractor",
- "111NN0400X": "Neurology Chiropractor",
- "111NN1001X": "Nutrition Chiropractor",
- "111NP0017X": "Pediatric Chiropractor",
- "111NR0200X": "Radiology Chiropractor",
- "111NR0400X": "Rehabilitation Chiropractor",
- "111NS0005X": "Sports Physician Chiropractor",
- "111NT0100X": "Thermography Chiropractor",
- "111NX0100X": "Occupational Health Chiropractor",
- "111NX0800X": "Orthopedic Chiropractor",
- "122300000X": "Dentist",
- "1223D0001X": "Public Health Dentist",
- "1223D0004X": "Dentist Anesthesiologist",
- "1223E0200X": "Endodontist",
- "1223G0001X": "General Practice Dentistry",
- "1223P0106X": "Oral and Maxillofacial Pathology Dentist",
- "1223P0221X": "Pediatric Dentist",
- "1223P0300X": "Periodontist",
- "1223P0700X": "Prosthodontist",
- "1223S0112X": "Oral and Maxillofacial Surgery (Dentist)",
- "1223X0008X": "Oral and Maxillofacial Radiology Dentist",
- "1223X0400X": "Orthodontics and Dentofacial Orthopedic Dentist",
- "1223X2210X": "Orofacial Pain Dentist",
- "122400000X": "Denturist",
- "124Q00000X": "Dental Hygienist",
- "125J00000X": "Dental Therapist",
- "125K00000X": "Advanced Practice Dental Therapist",
- "125Q00000X": "Oral Medicinist",
- "126800000X": "Dental Assistant",
- "126900000X": "Dental Laboratory Technician",
- "132700000X": "Dietary Manager",
- "133N00000X": "Nutritionist",
- "133NN1002X": "Nutrition Education Nutritionist",
- "133V00000X": "Registered Dietitian",
- "133VN1004X": "Pediatric Nutrition Registered Dietitian",
- "133VN1005X": "Renal Nutrition Registered Dietitian",
- "133VN1006X": "Metabolic Nutrition Registered Dietitian",
- "133VN1101X": "Gerontological Nutrition Registered Dietitian",
- "133VN1201X": "Obesity and Weight Management Nutrition Registered Dietitian",
- "133VN1301X": "Oncology Nutrition Registered Dietitian",
- "133VN1401X": "Pediatric Critical Care Nutrition Registered Dietitian",
- "133VN1501X": "Sports Dietetics Nutrition Registered Dietitian",
- "136A00000X": "Registered Dietetic Technician",
- "146D00000X": "Personal Emergency Response Attendant",
- "146L00000X": "Paramedic",
- "146M00000X": "Intermediate Emergency Medical Technician",
- "146N00000X": "Basic Emergency Medical Technician",
- "152W00000X": "Optometrist",
- "152WC0802X": "Corneal and Contact Management Optometrist",
- "152WL0500X": "Low Vision Rehabilitation Optometrist",
- "152WP0200X": "Pediatric Optometrist",
- "152WS0006X": "Sports Vision Optometrist",
- "152WV0400X": "Vision Therapy Optometrist",
- "152WX0102X": "Occupational Vision Optometrist",
- "156F00000X": "Technician/Technologist",
- "156FC0800X": "Contact Lens Technician/Technologist",
- "156FC0801X": "Contact Lens Fitter",
- "156FX1100X": "Ophthalmic Technician/Technologist",
- "156FX1101X": "Ophthalmic Assistant",
- "156FX1201X": "Optometric Assistant Technician",
- "156FX1202X": "Optometric Technician",
- "156FX1700X": "Ocularist",
- "156FX1800X": "Optician",
- "156FX1900X": "Orthoptist",
- "163W00000X": "Registered Nurse",
- "163WA0400X": "Addiction (Substance Use Disorder) Registered Nurse",
- "163WA2000X": "Administrator Registered Nurse",
- "163WC0200X": "Critical Care Medicine Registered Nurse",
- "163WC0400X": "Case Management Registered Nurse",
- "163WC1400X": "College Health Registered Nurse",
- "163WC1500X": "Community Health Registered Nurse",
- "163WC1600X": "Continuing Education/Staff Development Registered Nurse",
- "163WC2100X": "Continence Care Registered Nurse",
- "163WC3500X": "Cardiac Rehabilitation Registered Nurse",
- "163WD0400X": "Diabetes Educator Registered Nurse",
- "163WD1100X": "Peritoneal Dialysis Registered Nurse",
- "163WE0003X": "Emergency Registered Nurse",
- "163WE0900X": "Enterostomal Therapy Registered Nurse",
- "163WF0300X": "Flight Registered Nurse",
- "163WG0000X": "General Practice Registered Nurse",
- "163WG0100X": "Gastroenterology Registered Nurse",
- "163WG0600X": "Gerontology Registered Nurse",
- "163WH0200X": "Home Health Registered Nurse",
- "163WH0500X": "Hemodialysis Registered Nurse",
- "163WH1000X": "Hospice Registered Nurse",
- "163WI0500X": "Infusion Therapy Registered Nurse",
- "163WI0600X": "Infection Control Registered Nurse",
- "163WL0100X": "Lactation Consultant (Registered Nurse)",
- "163WM0102X": "Maternal Newborn Registered Nurse",
- "163WM0705X": "Medical-Surgical Registered Nurse",
- "163WM1400X": "Nurse Massage Therapist (NMT)",
- "163WN0002X": "Neonatal Intensive Care Registered Nurse",
- "163WN0003X": "Low-Risk Neonatal Registered Nurse",
- "163WN0300X": "Nephrology Registered Nurse",
- "163WN0800X": "Neuroscience Registered Nurse",
- "163WN1003X": "Nutrition Support Registered Nurse",
- "163WP0000X": "Pain Management Registered Nurse",
- "163WP0200X": "Pediatric Registered Nurse",
- "163WP0218X": "Pediatric Oncology Registered Nurse",
- "163WP0807X": "Child & Adolescent Psychiatric/Mental Health Registered Nurse",
- "163WP0808X": "Psychiatric/Mental Health Registered Nurse",
- "163WP0809X": "Adult Psychiatric/Mental Health Registered Nurse",
- "163WP1700X": "Perinatal Registered Nurse",
- "163WP2201X": "Ambulatory Care Registered Nurse",
- "163WR0006X": "Registered Nurse First Assistant",
- "163WR0400X": "Rehabilitation Registered Nurse",
- "163WR1000X": "Reproductive Endocrinology/Infertility Registered Nurse",
- "163WS0121X": "Plastic Surgery Registered Nurse",
- "163WS0200X": "School Registered Nurse",
- "163WU0100X": "Urology Registered Nurse",
- "163WW0000X": "Wound Care Registered Nurse",
- "163WW0101X": "Ambulatory Women's Health Care Registered Nurse",
- "163WX0002X": "High-Risk Obstetric Registered Nurse",
- "163WX0003X": "Inpatient Obstetric Registered Nurse",
- "163WX0106X": "Occupational Health Registered Nurse",
- "163WX0200X": "Oncology Registered Nurse",
- "163WX0601X": "Otorhinolaryngology & Head-Neck Registered Nurse",
- "163WX0800X": "Orthopedic Registered Nurse",
- "163WX1100X": "Ophthalmic Registered Nurse",
- "163WX1500X": "Ostomy Care Registered Nurse",
- "164W00000X": "Licensed Practical Nurse",
- "164X00000X": "Licensed Vocational Nurse",
- "167G00000X": "Licensed Psychiatric Technician",
- "170100000X": "Ph.D. Medical Genetics",
- "170300000X": "Genetic Counselor (M.S.)",
- "171000000X": "Military Health Care Provider",
- "1710I1002X": "Independent Duty Corpsman",
- "1710I1003X": "Independent Duty Medical Technicians",
- "171100000X": "Acupuncturist",
- "171400000X": "Health & Wellness Coach",
- "171M00000X": "Case Manager/Care Coordinator",
- "171R00000X": "Interpreter",
- "171W00000X": "Contractor",
- "171WH0202X": "Home Modifications Contractor",
- "171WV0202X": "Vehicle Modifications Contractor",
- "172A00000X": "Driver",
- "172M00000X": "Mechanotherapist",
- "172P00000X": "Naprapath",
- "172V00000X": "Community Health Worker",
- "173000000X": "Legal Medicine",
- "173C00000X": "Reflexologist",
- "173F00000X": "Sleep Specialist (PhD)",
- "174200000X": "Meals Provider",
- "174400000X": "Specialist",
- "1744G0900X": "Graphics Designer",
- "1744P3200X": "Prosthetics Case Management",
- "1744R1102X": "Research Study Specialist",
- "1744R1103X": "Research Study Abstracter/Coder",
- "174H00000X": "Health Educator",
- "174M00000X": "Veterinarian",
- "174MM1900X": "Medical Research Veterinarian",
- "174N00000X": "Lactation Consultant (Non-RN)",
- "174V00000X": "Clinical Ethicist",
- "175F00000X": "Naturopath",
- "175L00000X": "Homeopath",
- "175M00000X": "Lay Midwife",
- "175T00000X": "Peer Specialist",
- "176B00000X": "Midwife",
- "176P00000X": "Funeral Director",
- "177F00000X": "Lodging Provider",
- "183500000X": "Pharmacist",
- "1835C0205X": "Critical Care Pharmacist",
- "1835G0000X": "Deactivated - Pharmacist",
- "1835G0303X": "Geriatric Pharmacist",
- "1835N0905X": "Nuclear Pharmacist",
- "1835N1003X": "Nutrition Support Pharmacist",
- "1835P0018X": "Pharmacist Clinician (PhC)/ Clinical Pharmacy Specialist",
- "1835P0200X": "Pediatric Pharmacist",
- "1835P1200X": "Pharmacotherapy Pharmacist",
- "1835P1300X": "Psychiatric Pharmacist",
- "1835P2201X": "Ambulatory Care Pharmacist",
- "1835X0200X": "Oncology Pharmacist",
- "183700000X": "Pharmacy Technician",
- "193200000X": "Multi-Specialty Group",
- "193400000X": "Single Specialty Group",
- "202C00000X": "Independent Medical Examiner Physician",
- "202D00000X": "Integrative Medicine",
- "202K00000X": "Phlebology Physician",
- "204C00000X": "Sports Medicine (Neuromusculoskeletal Medicine) Physician",
- "204D00000X": "Neuromusculoskeletal Medicine & OMM Physician",
- "204E00000X": "Oral & Maxillofacial Surgery (D.M.D.)",
- "204F00000X": "Transplant Surgery Physician",
- "204R00000X": "Electrodiagnostic Medicine Physician",
- "207K00000X": "Allergy & Immunology Physician",
- "207KA0200X": "Allergy Physician",
- "207KI0005X": "Clinical & Laboratory Immunology (Allergy & Immunology) Physician",
- "207L00000X": "Anesthesiology Physician",
- "207LA0401X": "Addiction Medicine (Anesthesiology) Physician",
- "207LC0200X": "Critical Care Medicine (Anesthesiology) Physician",
- "207LH0002X": "Hospice and Palliative Medicine (Anesthesiology) Physician",
- "207LP2900X": "Pain Medicine (Anesthesiology) Physician",
- "207LP3000X": "Pediatric Anesthesiology Physician",
- "207N00000X": "Dermatology Physician",
- "207ND0101X": "MOHS-Micrographic Surgery Physician",
- "207ND0900X": "Dermatopathology Physician",
- "207NI0002X": "Clinical & Laboratory Dermatological Immunology Physician",
- "207NP0225X": "Pediatric Dermatology Physician",
- "207NS0135X": "Procedural Dermatology Physician",
- "207P00000X": "Emergency Medicine Physician",
- "207PE0004X": "Emergency Medical Services (Emergency Medicine) Physician",
- "207PE0005X": "Undersea and Hyperbaric Medicine (Emergency Medicine) Physician",
- "207PH0002X": "Hospice and Palliative Medicine (Emergency Medicine) Physician",
- "207PP0204X": "Pediatric Emergency Medicine (Emergency Medicine) Physician",
- "207PS0010X": "Sports Medicine (Emergency Medicine) Physician",
- "207PT0002X": "Medical Toxicology (Emergency Medicine) Physician",
- "207Q00000X": "Family Medicine Physician",
- "207QA0000X": "Adolescent Medicine (Family Medicine) Physician",
- "207QA0401X": "Addiction Medicine (Family Medicine) Physician",
- "207QA0505X": "Adult Medicine Physician",
- "207QB0002X": "Obesity Medicine (Family Medicine) Physician",
- "207QG0300X": "Geriatric Medicine (Family Medicine) Physician",
- "207QH0002X": "Hospice and Palliative Medicine (Family Medicine) Physician",
- "207QS0010X": "Sports Medicine (Family Medicine) Physician",
- "207QS1201X": "Sleep Medicine (Family Medicine) Physician",
- "207R00000X": "Internal Medicine Physician",
- "207RA0000X": "Adolescent Medicine (Internal Medicine) Physician",
- "207RA0001X": "Advanced Heart Failure and Transplant Cardiology Physician",
- "207RA0002X": "Adult Congenital Heart Disease Physician",
- "207RA0201X": "Allergy & Immunology (Internal Medicine) Physician",
- "207RA0401X": "Addiction Medicine (Internal Medicine) Physician",
- "207RB0002X": "Obesity Medicine (Internal Medicine) Physician",
- "207RC0000X": "Cardiovascular Disease Physician",
- "207RC0001X": "Clinical Cardiac Electrophysiology Physician",
- "207RC0200X": "Critical Care Medicine (Internal Medicine) Physician",
- "207RE0101X": "Endocrinology, Diabetes & Metabolism Physician",
- "207RG0100X": "Gastroenterology Physician",
- "207RG0300X": "Geriatric Medicine (Internal Medicine) Physician",
- "207RH0000X": "Hematology (Internal Medicine) Physician",
- "207RH0002X": "Hospice and Palliative Medicine (Internal Medicine) Physician",
- "207RH0003X": "Hematology & Oncology Physician",
- "207RH0005X": "Hypertension Specialist Physician",
- "207RI0001X": "Clinical & Laboratory Immunology (Internal Medicine) Physician",
- "207RI0008X": "Hepatology Physician",
- "207RI0011X": "Interventional Cardiology Physician",
- "207RI0200X": "Infectious Disease Physician",
- "207RM1200X": "Magnetic Resonance Imaging (MRI) Internal Medicine Physician",
- "207RN0300X": "Nephrology Physician",
- "207RP1001X": "Pulmonary Disease Physician",
- "207RR0500X": "Rheumatology Physician",
- "207RS0010X": "Sports Medicine (Internal Medicine) Physician",
- "207RS0012X": "Sleep Medicine (Internal Medicine) Physician",
- "207RT0003X": "Transplant Hepatology Physician",
- "207RX0202X": "Medical Oncology Physician",
- "207SC0300X": "Clinical Cytogenetics Physician",
- "207SG0201X": "Clinical Genetics (M.D.) Physician",
- "207SG0202X": "Clinical Biochemical Genetics Physician",
- "207SG0203X": "Clinical Molecular Genetics Physician",
- "207SG0205X": "Ph.D. Medical Genetics Physician",
- "207SM0001X": "Molecular Genetic Pathology (Medical Genetics) Physician",
- "207T00000X": "Neurological Surgery Physician",
- "207U00000X": "Nuclear Medicine Physician",
- "207UN0901X": "Nuclear Cardiology Physician",
- "207UN0902X": "Nuclear Imaging & Therapy Physician",
- "207UN0903X": "In Vivo & In Vitro Nuclear Medicine Physician",
- "207V00000X": "Obstetrics & Gynecology Physician",
- "207VB0002X": "Obesity Medicine (Obstetrics & Gynecology) Physician",
- "207VC0200X": "Critical Care Medicine (Obstetrics & Gynecology) Physician",
- "207VC0300X": "Complex Family Planning",
- "207VE0102X": "Reproductive Endocrinology Physician",
- "207VF0040X": "Female Pelvic Medicine and Reconstructive Surgery (Obstetrics & Gynecology) Physician",
- "207VG0400X": "Gynecology Physician",
- "207VH0002X": "Hospice and Palliative Medicine (Obstetrics & Gynecology) Physician",
- "207VM0101X": "Maternal & Fetal Medicine Physician",
- "207VX0000X": "Obstetrics Physician",
- "207VX0201X": "Gynecologic Oncology Physician",
- "207W00000X": "Ophthalmology Physician",
- "207WX0009X": "Glaucoma Specialist (Ophthalmology) Physician",
- "207WX0107X": "Retina Specialist (Ophthalmology) Physician",
- "207WX0108X": "Uveitis and Ocular Inflammatory Disease (Ophthalmology) Physician",
- "207WX0109X": "Neuro-ophthalmology Physician",
- "207WX0110X": "Pediatric Ophthalmology and Strabismus Specialist Physician Physician",
- "207WX0120X": "Cornea and External Diseases Specialist Physician",
- "207WX0200X": "Ophthalmic Plastic and Reconstructive Surgery Physician",
- "207X00000X": "Orthopaedic Surgery Physician",
- "207XP3100X": "Pediatric Orthopaedic Surgery Physician",
- "207XS0106X": "Orthopaedic Hand Surgery Physician",
- "207XS0114X": "Adult Reconstructive Orthopaedic Surgery Physician",
- "207XS0117X": "Orthopaedic Surgery of the Spine Physician",
- "207XX0004X": "Orthopaedic Foot and Ankle Surgery Physician",
- "207XX0005X": "Sports Medicine (Orthopaedic Surgery) Physician",
- "207XX0801X": "Orthopaedic Trauma Physician",
- "207Y00000X": "Otolaryngology Physician",
- "207YP0228X": "Pediatric Otolaryngology Physician",
- "207YS0012X": "Sleep Medicine (Otolaryngology) Physician",
- "207YS0123X": "Facial Plastic Surgery Physician",
- "207YX0007X": "Plastic Surgery within the Head & Neck (Otolaryngology) Physician",
- "207YX0602X": "Otolaryngic Allergy Physician",
- "207YX0901X": "Otology & Neurotology Physician",
- "207YX0905X": "Otolaryngology/Facial Plastic Surgery Physician",
- "207ZB0001X": "Blood Banking & Transfusion Medicine Physician",
- "207ZC0006X": "Clinical Pathology Physician",
- "207ZC0008X": "Clinical Informatics (Pathology) Physician",
- "207ZC0500X": "Cytopathology Physician",
- "207ZD0900X": "Dermatopathology (Pathology) Physician",
- "207ZF0201X": "Forensic Pathology Physician",
- "207ZH0000X": "Hematology (Pathology) Physician",
- "207ZI0100X": "Immunopathology Physician",
- "207ZM0300X": "Medical Microbiology Physician",
- "207ZN0500X": "Neuropathology Physician",
- "207ZP0007X": "Molecular Genetic Pathology (Pathology) Physician",
- "207ZP0101X": "Anatomic Pathology Physician",
- "207ZP0102X": "Anatomic Pathology & Clinical Pathology Physician",
- "207ZP0104X": "Chemical Pathology Physician",
- "207ZP0105X": "Clinical Pathology/Laboratory Medicine Physician",
- "207ZP0213X": "Pediatric Pathology Physician",
- "208000000X": "Pediatrics Physician",
- "2080A0000X": "Pediatric Adolescent Medicine Physician",
- "2080B0002X": "Pediatric Obesity Medicine Physician",
- "2080C0008X": "Child Abuse Pediatrics Physician",
- "2080H0002X": "Pediatric Hospice and Palliative Medicine Physician",
- "2080I0007X": "Pediatric Clinical & Laboratory Immunology Physician",
- "2080N0001X": "Neonatal-Perinatal Medicine Physician",
- "2080P0006X": "Developmental - Behavioral Pediatrics Physician",
- "2080P0008X": "Pediatric Neurodevelopmental Disabilities Physician",
- "2080P0201X": "Pediatric Allergy/Immunology Physician",
- "2080P0202X": "Pediatric Cardiology Physician",
- "2080P0203X": "Pediatric Critical Care Medicine Physician",
- "2080P0204X": "Pediatric Emergency Medicine (Pediatrics) Physician",
- "2080P0205X": "Pediatric Endocrinology Physician",
- "2080P0206X": "Pediatric Gastroenterology Physician",
- "2080P0207X": "Pediatric Hematology & Oncology Physician",
- "2080P0208X": "Pediatric Infectious Diseases Physician",
- "2080P0210X": "Pediatric Nephrology Physician",
- "2080P0214X": "Pediatric Pulmonology Physician",
- "2080P0216X": "Pediatric Rheumatology Physician",
- "2080S0010X": "Pediatric Sports Medicine Physician",
- "2080S0012X": "Pediatric Sleep Medicine Physician",
- "2080T0002X": "Pediatric Medical Toxicology Physician",
- "2080T0004X": "Pediatric Transplant Hepatology Physician",
- "208100000X": "Physical Medicine & Rehabilitation Physician",
- "2081H0002X": "Hospice and Palliative Medicine (Physical Medicine & Rehabilitation) Physician",
- "2081N0008X": "Neuromuscular Medicine (Physical Medicine & Rehabilitation) Physician",
- "2081P0004X": "Spinal Cord Injury Medicine Physician",
- "2081P0010X": "Pediatric Rehabilitation Medicine Physician",
- "2081P0301X": "Brain Injury Medicine (Physical Medicine & Rehabilitation) Physician",
- "2081P2900X": "Pain Medicine (Physical Medicine & Rehabilitation) Physician",
- "2081S0010X": "Sports Medicine (Physical Medicine & Rehabilitation) Physician",
- "208200000X": "Plastic Surgery Physician",
- "2082S0099X": "Plastic Surgery Within the Head and Neck (Plastic Surgery) Physician",
- "2082S0105X": "Surgery of the Hand (Plastic Surgery) Physician",
- "2083A0100X": "Aerospace Medicine Physician",
- "2083A0300X": "Addiction Medicine (Preventive Medicine) Physician",
- "2083B0002X": "Obesity Medicine (Preventive Medicine) Physician",
- "2083C0008X": "Clinical Informatics Physician",
- "2083P0011X": "Undersea and Hyperbaric Medicine (Preventive Medicine) Physician",
- "2083P0500X": "Preventive Medicine/Occupational Environmental Medicine Physician",
- "2083P0901X": "Public Health & General Preventive Medicine Physician",
- "2083S0010X": "Sports Medicine (Preventive Medicine) Physician",
- "2083T0002X": "Medical Toxicology (Preventive Medicine) Physician",
- "2083X0100X": "Occupational Medicine Physician",
- "2084A0401X": "Addiction Medicine (Psychiatry & Neurology) Physician",
- "2084A2900X": "Neurocritical Care Physician",
- "2084B0002X": "Obesity Medicine (Psychiatry & Neurology) Physician",
- "2084B0040X": "Behavioral Neurology & Neuropsychiatry Physician",
- "2084D0003X": "Diagnostic Neuroimaging (Psychiatry & Neurology) Physician",
- "2084E0001X": "Epilepsy Physician",
- "2084F0202X": "Forensic Psychiatry Physician",
- "2084H0002X": "Hospice and Palliative Medicine (Psychiatry & Neurology) Physician",
- "2084N0008X": "Neuromuscular Medicine (Psychiatry & Neurology) Physician",
- "2084N0400X": "Neurology Physician",
- "2084N0402X": "Neurology with Special Qualifications in Child Neurology Physician",
- "2084N0600X": "Clinical Neurophysiology Physician",
- "2084P0005X": "Neurodevelopmental Disabilities Physician Physician",
- "2084P0015X": "Psychosomatic Medicine Physician",
- "2084P0301X": "Brain Injury Medicine (Psychiatry & Neurology) Physician",
- "2084P0800X": "Psychiatry Physician",
- "2084P0802X": "Addiction Psychiatry Physician",
- "2084P0804X": "Child & Adolescent Psychiatry Physician",
- "2084P0805X": "Geriatric Psychiatry Physician",
- "2084P2900X": "Pain Medicine (Psychiatry & Neurology) Physician",
- "2084S0010X": "Sports Medicine (Psychiatry & Neurology) Physician",
- "2084S0012X": "Sleep Medicine (Psychiatry & Neurology) Physician",
- "2084V0102X": "Vascular Neurology Physician",
- "2085B0100X": "Body Imaging Physician",
- "2085D0003X": "Diagnostic Neuroimaging (Radiology) Physician",
- "2085H0002X": "Hospice and Palliative Medicine (Radiology) Physician",
- "2085N0700X": "Neuroradiology Physician",
- "2085N0904X": "Nuclear Radiology Physician",
- "2085P0229X": "Pediatric Radiology Physician",
- "2085R0001X": "Radiation Oncology Physician",
- "2085R0202X": "Diagnostic Radiology Physician",
- "2085R0203X": "Therapeutic Radiology Physician",
- "2085R0204X": "Vascular & Interventional Radiology Physician",
- "2085R0205X": "Radiological Physics Physician",
- "2085U0001X": "Diagnostic Ultrasound Physician",
- "208600000X": "Surgery Physician",
- "2086H0002X": "Hospice and Palliative Medicine (Surgery) Physician",
- "2086S0102X": "Surgical Critical Care Physician",
- "2086S0105X": "Surgery of the Hand (Surgery) Physician",
- "2086S0120X": "Pediatric Surgery Physician",
- "2086S0122X": "Plastic and Reconstructive Surgery Physician",
- "2086S0127X": "Trauma Surgery Physician",
- "2086S0129X": "Vascular Surgery Physician",
- "2086X0206X": "Surgical Oncology Physician",
- "208800000X": "Urology Physician",
- "2088F0040X": "Female Pelvic Medicine and Reconstructive Surgery (Urology) Physician",
- "2088P0231X": "Pediatric Urology Physician",
- "208C00000X": "Colon & Rectal Surgery Physician",
- "208D00000X": "General Practice Physician",
- "208G00000X": "Thoracic Surgery (Cardiothoracic Vascular Surgery) Physician",
- "208M00000X": "Hospitalist Physician",
- "208U00000X": "Clinical Pharmacology Physician",
- "208VP0000X": "Pain Medicine Physician",
- "208VP0014X": "Interventional Pain Medicine Physician",
- "209800000X": "Legal Medicine (M.D./D.O.) Physician",
- "211D00000X": "Podiatric Assistant",
- "213E00000X": "Podiatrist",
- "213EG0000X": "Deactivated - Podiatrist",
- "213EP0504X": "Public Medicine Podiatrist",
- "213EP1101X": "Primary Podiatric Medicine Podiatrist",
- "213ER0200X": "Radiology Podiatrist",
- "213ES0000X": "Sports Medicine Podiatrist",
- "213ES0103X": "Foot & Ankle Surgery Podiatrist",
- "213ES0131X": "Foot Surgery Podiatrist",
- "221700000X": "Art Therapist",
- "222Q00000X": "Developmental Therapist",
- "222Z00000X": "Orthotist",
- "224900000X": "Mastectomy Fitter",
- "224L00000X": "Pedorthist",
- "224P00000X": "Prosthetist",
- "224Y00000X": "Clinical Exercise Physiologist",
- "224Z00000X": "Occupational Therapy Assistant",
- "224ZE0001X": "Environmental Modification Occupational Therapy Assistant",
- "224ZF0002X": "Feeding, Eating & Swallowing Occupational Therapy Assistant",
- "224ZL0004X": "Low Vision Occupational Therapy Assistant",
- "224ZR0403X": "Driving and Community Mobility Occupational Therapy Assistant",
- "225000000X": "Orthotic Fitter",
- "225100000X": "Physical Therapist",
- "2251C2600X": "Cardiopulmonary Physical Therapist",
- "2251E1200X": "Ergonomics Physical Therapist",
- "2251E1300X": "Clinical Electrophysiology Physical Therapist",
- "2251G0304X": "Geriatric Physical Therapist",
- "2251H1200X": "Hand Physical Therapist",
- "2251H1300X": "Human Factors Physical Therapist",
- "2251N0400X": "Neurology Physical Therapist",
- "2251P0200X": "Pediatric Physical Therapist",
- "2251S0007X": "Sports Physical Therapist",
- "2251X0800X": "Orthopedic Physical Therapist",
- "225200000X": "Physical Therapy Assistant",
- "225400000X": "Rehabilitation Practitioner",
- "225500000X": "Respiratory/Developmental/Rehabilitative Specialist/Technologist",
- "2255A2300X": "Athletic Trainer",
- "2255R0406X": "Blind Rehabilitation Specialist/Technologist",
- "225600000X": "Dance Therapist",
- "225700000X": "Massage Therapist",
- "225800000X": "Recreation Therapist",
- "225A00000X": "Music Therapist",
- "225B00000X": "Pulmonary Function Technologist",
- "225C00000X": "Rehabilitation Counselor",
- "225CA2400X": "Assistive Technology Practitioner Rehabilitation Counselor",
- "225CA2500X": "Assistive Technology Supplier Rehabilitation Counselor",
- "225CX0006X": "Orientation and Mobility Training Rehabilitation Counselor",
- "225X00000X": "Occupational Therapist",
- "225XE0001X": "Environmental Modification Occupational Therapist",
- "225XE1200X": "Ergonomics Occupational Therapist",
- "225XF0002X": "Feeding, Eating & Swallowing Occupational Therapist",
- "225XG0600X": "Gerontology Occupational Therapist",
- "225XH1200X": "Hand Occupational Therapist",
- "225XH1300X": "Human Factors Occupational Therapist",
- "225XL0004X": "Low Vision Occupational Therapist",
- "225XM0800X": "Mental Health Occupational Therapist",
- "225XN1300X": "Neurorehabilitation Occupational Therapist",
- "225XP0019X": "Physical Rehabilitation Occupational Therapist",
- "225XP0200X": "Pediatric Occupational Therapist",
- "225XR0403X": "Driving and Community Mobility Occupational Therapist",
- "226000000X": "Recreational Therapist Assistant",
- "226300000X": "Kinesiotherapist",
- "227800000X": "Certified Respiratory Therapist",
- "2278C0205X": "Critical Care Certified Respiratory Therapist",
- "2278E0002X": "Emergency Care Certified Respiratory Therapist",
- "2278E1000X": "Educational Certified Respiratory Therapist",
- "2278G0305X": "Geriatric Care Certified Respiratory Therapist",
- "2278G1100X": "General Care Certified Respiratory Therapist",
- "2278H0200X": "Home Health Certified Respiratory Therapist",
- "2278P1004X": "Pulmonary Diagnostics Certified Respiratory Therapist",
- "2278P1005X": "Pulmonary Rehabilitation Certified Respiratory Therapist",
- "2278P1006X": "Pulmonary Function Technologist Certified Respiratory Therapist",
- "2278P3800X": "Palliative/Hospice Certified Respiratory Therapist",
- "2278P3900X": "Neonatal/Pediatric Certified Respiratory Therapist",
- "2278P4000X": "Patient Transport Certified Respiratory Therapist",
- "2278S1500X": "SNF/Subacute Care Certified Respiratory Therapist",
- "227900000X": "Registered Respiratory Therapist",
- "2279C0205X": "Critical Care Registered Respiratory Therapist",
- "2279E0002X": "Emergency Care Registered Respiratory Therapist",
- "2279E1000X": "Educational Registered Respiratory Therapist",
- "2279G0305X": "Geriatric Care Registered Respiratory Therapist",
- "2279G1100X": "General Care Registered Respiratory Therapist",
- "2279H0200X": "Home Health Registered Respiratory Therapist",
- "2279P1004X": "Pulmonary Diagnostics Registered Respiratory Therapist",
- "2279P1005X": "Pulmonary Rehabilitation Registered Respiratory Therapist",
- "2279P1006X": "Pulmonary Function Technologist Registered Respiratory Therapist",
- "2279P3800X": "Palliative/Hospice Registered Respiratory Therapist",
- "2279P3900X": "Neonatal/Pediatric Registered Respiratory Therapist",
- "2279P4000X": "Patient Transport Registered Respiratory Therapist",
- "2279S1500X": "SNF/Subacute Care Registered Respiratory Therapist",
- "229N00000X": "Anaplastologist",
- "231H00000X": "Audiologist",
- "231HA2400X": "Assistive Technology Practitioner Audiologist",
- "231HA2500X": "Assistive Technology Supplier Audiologist",
- "235500000X": "Speech/Language/Hearing Specialist/Technologist",
- "2355A2700X": "Audiology Assistant",
- "2355S0801X": "Speech-Language Assistant",
- "235Z00000X": "Speech-Language Pathologist",
- "237600000X": "Audiologist-Hearing Aid Fitter",
- "237700000X": "Hearing Instrument Specialist",
- "242T00000X": "Perfusionist",
- "243U00000X": "Radiology Practitioner Assistant",
- "246Q00000X": "Pathology Specialist/Technologist",
- "246QB0000X": "Blood Banking Specialist/Technologist",
- "246QC1000X": "Chemistry Pathology Specialist/Technologist",
- "246QC2700X": "Cytotechnology Specialist/Technologist",
- "246QH0000X": "Hematology Specialist/Technologist",
- "246QH0401X": "Hemapheresis Practitioner",
- "246QH0600X": "Histology Specialist/Technologist",
- "246QI0000X": "Immunology Pathology Specialist/Technologist",
- "246QL0900X": "Laboratory Management Specialist/Technologist",
- "246QL0901X": "Diplomate Laboratory Management Specialist/Technologist",
- "246QM0706X": "Medical Technologist",
- "246QM0900X": "Microbiology Specialist/Technologist",
- "246R00000X": "Pathology Technician",
- "246RH0600X": "Histology Technician",
- "246RM2200X": "Medical Laboratory Technician",
- "246RP1900X": "Phlebotomy Technician",
- "246W00000X": "Cardiology Technician",
- "246X00000X": "Cardiovascular Specialist/Technologist",
- "246XC2901X": "Cardiovascular Invasive Specialist/Technologist",
- "246XC2903X": "Vascular Specialist/Technologist",
- "246XS1301X": "Sonography Specialist/Technologist",
- "246Y00000X": "Health Information Specialist/Technologist",
- "246YC3301X": "Hospital Based Coding Specialist",
- "246YC3302X": "Physician Office Based Coding Specialist",
- "246YR1600X": "Registered Record Administrator",
- "246Z00000X": "Other Specialist/Technologist",
- "246ZA2600X": "Medical Art Specialist/Technologist",
- "246ZB0301X": "Biomedical Engineer",
- "246ZB0302X": "Biomedical Photographer",
- "246ZB0500X": "Biochemist",
- "246ZB0600X": "Biostatiscian",
- "246ZC0007X": "Surgical Assistant",
- "246ZE0500X": "EEG Specialist/Technologist",
- "246ZE0600X": "Electroneurodiagnostic Specialist/Technologist",
- "246ZG0701X": "Graphics Methods Specialist/Technologist",
- "246ZG1000X": "Medical Geneticist (PhD) Specialist/Technologist",
- "246ZI1000X": "Medical Illustrator",
- "246ZN0300X": "Nephrology Specialist/Technologist",
- "246ZS0410X": "Surgical Technologist",
- "246ZX2200X": "Orthopedic Assistant",
- "247000000X": "Health Information Technician",
- "2470A2800X": "Assistant Health Information Record Technician",
- "247100000X": "Radiologic Technologist",
- "2471B0102X": "Bone Densitometry Radiologic Technologist",
- "2471C1101X": "Cardiovascular-Interventional Technology Radiologic Technologist",
- "2471C1106X": "Cardiac-Interventional Technology Radiologic Technologist",
- "2471C3401X": "Computed Tomography Radiologic Technologist",
- "2471C3402X": "Radiography Radiologic Technologist",
- "2471M1202X": "Magnetic Resonance Imaging Radiologic Technologist",
- "2471M2300X": "Mammography Radiologic Technologist",
- "2471N0900X": "Nuclear Medicine Technology Radiologic Technologist",
- "2471Q0001X": "Quality Management Radiologic Technologist",
- "2471R0002X": "Radiation Therapy Radiologic Technologist",
- "2471S1302X": "Sonography Radiologic Technologist",
- "2471V0105X": "Vascular Sonography Radiologic Technologist",
- "2471V0106X": "Vascular-Interventional Technology Radiologic Technologist",
- "247200000X": "Other Technician",
- "2472B0301X": "Biomedical Engineering Technician",
- "2472D0500X": "Darkroom Technician",
- "2472E0500X": "EEG Technician",
- "2472R0900X": "Renal Dialysis Technician",
- "2472V0600X": "Veterinary Technician",
- "247ZC0005X": "Clinical Laboratory Director (Non-physician)",
- "251300000X": "Local Education Agency (LEA)",
- "251B00000X": "Case Management Agency",
- "251C00000X": "Developmentally Disabled Services Day Training Agency",
- "251E00000X": "Home Health Agency",
- "251F00000X": "Home Infusion Agency",
- "251G00000X": "Community Based Hospice Care Agency",
- "251J00000X": "Nursing Care Agency",
- "251K00000X": "Public Health or Welfare Agency",
- "251S00000X": "Community/Behavioral Health Agency",
- "251T00000X": "PACE Provider Organization",
- "251V00000X": "Voluntary or Charitable Agency",
- "251X00000X": "Supports Brokerage Agency",
- "252Y00000X": "Early Intervention Provider Agency",
- "253J00000X": "Foster Care Agency",
- "253Z00000X": "In Home Supportive Care Agency",
- "261Q00000X": "Clinic/Center",
- "261QA0005X": "Ambulatory Family Planning Facility",
- "261QA0006X": "Ambulatory Fertility Facility",
- "261QA0600X": "Adult Day Care Clinic/Center",
- "261QA0900X": "Amputee Clinic/Center",
- "261QA1903X": "Ambulatory Surgical Clinic/Center",
- "261QA3000X": "Augmentative Communication Clinic/Center",
- "261QB0400X": "Birthing Clinic/Center",
- "261QC0050X": "Critical Access Hospital Clinic/Center",
- "261QC1500X": "Community Health Clinic/Center",
- "261QC1800X": "Corporate Health Clinic/Center",
- "261QD0000X": "Dental Clinic/Center",
- "261QD1600X": "Developmental Disabilities Clinic/Center",
- "261QE0002X": "Emergency Care Clinic/Center",
- "261QE0700X": "End-Stage Renal Disease (ESRD) Treatment Clinic/Center",
- "261QE0800X": "Endoscopy Clinic/Center",
- "261QF0050X": "Non-Surgical Family Planning Clinic/Center",
- "261QF0400X": "Federally Qualified Health Center (FQHC)",
- "261QG0250X": "Genetics Clinic/Center",
- "261QH0100X": "Health Service Clinic/Center",
- "261QH0700X": "Hearing and Speech Clinic/Center",
- "261QI0500X": "Infusion Therapy Clinic/Center",
- "261QL0400X": "Lithotripsy Clinic/Center",
- "261QM0801X": "Mental Health Clinic/Center (Including Community Mental Health Center)",
- "261QM0850X": "Adult Mental Health Clinic/Center",
- "261QM0855X": "Adolescent and Children Mental Health Clinic/Center",
- "261QM1000X": "Migrant Health Clinic/Center",
- "261QM1100X": "Military/U.S. Coast Guard Outpatient Clinic/Center",
- "261QM1101X": "Military and U.S. Coast Guard Ambulatory Procedure Clinic/Center",
- "261QM1102X": "Military Outpatient Operational (Transportable) Component Clinic/Center",
- "261QM1103X": "Military Ambulatory Procedure Visits Operational (Transportable) Clinic/Center",
- "261QM1200X": "Magnetic Resonance Imaging (MRI) Clinic/Center",
- "261QM1300X": "Multi-Specialty Clinic/Center",
- "261QM2500X": "Medical Specialty Clinic/Center",
- "261QM2800X": "Methadone Clinic",
- "261QM3000X": "Medically Fragile Infants and Children Day Care",
- "261QP0904X": "Federal Public Health Clinic/Center",
- "261QP0905X": "State or Local Public Health Clinic/Center",
- "261QP1100X": "Podiatric Clinic/Center",
- "261QP2000X": "Physical Therapy Clinic/Center",
- "261QP2300X": "Primary Care Clinic/Center",
- "261QP2400X": "Prison Health Clinic/Center",
- "261QP3300X": "Pain Clinic/Center",
- "261QR0200X": "Radiology Clinic/Center",
- "261QR0206X": "Mammography Clinic/Center",
- "261QR0207X": "Mobile Mammography Clinic/Center",
- "261QR0208X": "Mobile Radiology Clinic/Center",
- "261QR0400X": "Rehabilitation Clinic/Center",
- "261QR0401X": "Comprehensive Outpatient Rehabilitation Facility (CORF)",
- "261QR0404X": "Cardiac Rehabilitation Clinic/Center",
- "261QR0405X": "Substance Use Disorder Rehabilitation Clinic/Center",
- "261QR0800X": "Recovery Care Clinic/Center",
- "261QR1100X": "Research Clinic/Center",
- "261QR1300X": "Rural Health Clinic/Center",
- "261QS0112X": "Oral and Maxillofacial Surgery Clinic/Center",
- "261QS0132X": "Ophthalmologic Surgery Clinic/Center",
- "261QS1000X": "Student Health Clinic/Center",
- "261QS1200X": "Sleep Disorder Diagnostic Clinic/Center",
- "261QU0200X": "Urgent Care Clinic/Center",
- "261QV0200X": "VA Clinic/Center",
- "261QX0100X": "Occupational Medicine Clinic/Center",
- "261QX0200X": "Oncology Clinic/Center",
- "261QX0203X": "Radiation Oncology Clinic/Center",
- "273100000X": "Epilepsy Hospital Unit",
- "273R00000X": "Psychiatric Hospital Unit",
- "273Y00000X": "Rehabilitation Hospital Unit",
- "275N00000X": "Medicare Defined Swing Bed Hospital Unit",
- "276400000X": "Substance Use Disorder Rehabilitation Hospital Unit",
- "281P00000X": "Chronic Disease Hospital",
- "281PC2000X": "Children' s Chronic Disease Hospital",
- "282E00000X": "Long Term Care Hospital",
- "282J00000X": "Religious Nonmedical Health Care Institution",
- "282N00000X": "General Acute Care Hospital",
- "282NC0060X": "Critical Access Hospital",
- "282NC2000X": "Children's Hospital",
- "282NR1301X": "Rural Acute Care Hospital",
- "282NW0100X": "Women's Hospital",
- "283Q00000X": "Psychiatric Hospital",
- "283X00000X": "Rehabilitation Hospital",
- "283XC2000X": "Children's Rehabilitation Hospital",
- "284300000X": "Special Hospital",
- "286500000X": "Military Hospital",
- "2865C1500X": "Deactivated - Military Hospital",
- "2865M2000X": "Military General Acute Care Hospital",
- "2865X1600X": "Operational (Transportable) Military General Acute Care Hospital",
- "287300000X": "Deactivated - Christian Science Sanitorium",
- "291900000X": "Military Clinical Medical Laboratory",
- "291U00000X": "Clinical Medical Laboratory",
- "292200000X": "Dental Laboratory",
- "293D00000X": "Physiological Laboratory",
- "302F00000X": "Exclusive Provider Organization",
- "302R00000X": "Health Maintenance Organization",
- "305R00000X": "Preferred Provider Organization",
- "305S00000X": "Point of Service",
- "310400000X": "Assisted Living Facility",
- "3104A0625X": "Assisted Living Facility (Mental Illness)",
- "3104A0630X": "Assisted Living Facility (Behavioral Disturbances)",
- "310500000X": "Mental Illness Intermediate Care Facility",
- "311500000X": "Alzheimer Center (Dementia Center)",
- "311Z00000X": "Custodial Care Facility",
- "311ZA0620X": "Adult Care Home Facility",
- "313M00000X": "Nursing Facility/Intermediate Care Facility",
- "314000000X": "Skilled Nursing Facility",
- "3140N1450X": "Pediatric Skilled Nursing Facility",
- "315D00000X": "Inpatient Hospice",
- "315P00000X": "Intellectual Disabilities Intermediate Care Facility",
- "317400000X": "Deactivated - Christian Science Facility",
- "320600000X": "Intellectual and/or Developmental Disabilities Residential Treatment Facility",
- "320700000X": "Physical Disabilities Residential Treatment Facility",
- "320800000X": "Mental Illness Community Based Residential Treatment Facility",
- "320900000X": "Intellectual and/or Developmental Disabilities Community Based Residential Treatment Facility",
- "322D00000X": "Emotionally Disturbed Childrens' Residential Treatment Facility",
- "323P00000X": "Psychiatric Residential Treatment Facility",
- "324500000X": "Substance Abuse Rehabilitation Facility",
- "3245S0500X": "Children's Substance Abuse Rehabilitation Facility",
- "331L00000X": "Blood Bank",
- "332000000X": "Military/U.S. Coast Guard Pharmacy",
- "332100000X": "Department of Veterans Affairs (VA) Pharmacy",
- "332800000X": "Indian Health Service/Tribal/Urban Indian Health (I/T/U) Pharmacy",
- "332900000X": "Non-Pharmacy Dispensing Site",
- "332B00000X": "Durable Medical Equipment & Medical Supplies",
- "332BC3200X": "Customized Equipment (DME)",
- "332BD1200X": "Dialysis Equipment & Supplies (DME)",
- "332BN1400X": "Nursing Facility Supplies (DME)",
- "332BP3500X": "Parenteral & Enteral Nutrition Supplies (DME)",
- "332BX2000X": "Oxygen Equipment & Supplies (DME)",
- "332G00000X": "Eye Bank",
- "332H00000X": "Eyewear Supplier",
- "332S00000X": "Hearing Aid Equipment",
- "332U00000X": "Home Delivered Meals",
- "333300000X": "Emergency Response System Companies",
- "333600000X": "Pharmacy",
- "3336C0002X": "Clinic Pharmacy",
- "3336C0003X": "Community/Retail Pharmacy",
- "3336C0004X": "Compounding Pharmacy",
- "3336H0001X": "Home Infusion Therapy Pharmacy",
- "3336I0012X": "Institutional Pharmacy",
- "3336L0003X": "Long Term Care Pharmacy",
- "3336M0002X": "Mail Order Pharmacy",
- "3336M0003X": "Managed Care Organization Pharmacy",
- "3336N0007X": "Nuclear Pharmacy",
- "3336S0011X": "Specialty Pharmacy",
- "335E00000X": "Prosthetic/Orthotic Supplier",
- "335G00000X": "Medical Foods Supplier",
- "335U00000X": "Organ Procurement Organization",
- "335V00000X": "Portable X-ray and/or Other Portable Diagnostic Imaging Supplier",
- "341600000X": "Ambulance",
- "3416A0800X": "Air Ambulance",
- "3416L0300X": "Land Ambulance",
- "3416S0300X": "Water Ambulance",
- "341800000X": "Military/U.S. Coast Guard Transport,",
- "3418M1110X": "Military or U.S. Coast Guard Ground Transport Ambulance",
- "3418M1120X": "Military or U.S. Coast Guard Air Transport Ambulance",
- "3418M1130X": "Military or U.S. Coast Guard Water Transport Ambulance",
- "342000000X": "Transportation Network Company",
- "343800000X": "Secured Medical Transport (VAN)",
- "343900000X": "Non-emergency Medical Transport (VAN)",
- "344600000X": "Taxi",
- "344800000X": "Air Carrier",
- "347B00000X": "Bus",
- "347C00000X": "Private Vehicle",
- "347D00000X": "Train",
- "347E00000X": "Transportation Broker",
- "363A00000X": "Physician Assistant",
- "363AM0700X": "Medical Physician Assistant",
- "363AS0400X": "Surgical Physician Assistant",
- "363L00000X": "Nurse Practitioner",
- "363LA2100X": "Acute Care Nurse Practitioner",
- "363LA2200X": "Adult Health Nurse Practitioner",
- "363LC0200X": "Critical Care Medicine Nurse Practitioner",
- "363LC1500X": "Community Health Nurse Practitioner",
- "363LF0000X": "Family Nurse Practitioner",
- "363LG0600X": "Gerontology Nurse Practitioner",
- "363LN0000X": "Neonatal Nurse Practitioner",
- "363LN0005X": "Critical Care Neonatal Nurse Practitioner",
- "363LP0200X": "Pediatric Nurse Practitioner",
- "363LP0222X": "Critical Care Pediatric Nurse Practitioner",
- "363LP0808X": "Psychiatric/Mental Health Nurse Practitioner",
- "363LP1700X": "Perinatal Nurse Practitioner",
- "363LP2300X": "Primary Care Nurse Practitioner",
- "363LS0200X": "School Nurse Practitioner",
- "363LW0102X": "Women's Health Nurse Practitioner",
- "363LX0001X": "Obstetrics & Gynecology Nurse Practitioner",
- "363LX0106X": "Occupational Health Nurse Practitioner",
- "364S00000X": "Clinical Nurse Specialist",
- "364SA2100X": "Acute Care Clinical Nurse Specialist",
- "364SA2200X": "Adult Health Clinical Nurse Specialist",
- "364SC0200X": "Critical Care Medicine Clinical Nurse Specialist",
- "364SC1501X": "Community Health/Public Health Clinical Nurse Specialist",
- "364SC2300X": "Chronic Care Clinical Nurse Specialist",
- "364SE0003X": "Emergency Clinical Nurse Specialist",
- "364SE1400X": "Ethics Clinical Nurse Specialist",
- "364SF0001X": "Family Health Clinical Nurse Specialist",
- "364SG0600X": "Gerontology Clinical Nurse Specialist",
- "364SH0200X": "Home Health Clinical Nurse Specialist",
- "364SH1100X": "Holistic Clinical Nurse Specialist",
- "364SI0800X": "Informatics Clinical Nurse Specialist",
- "364SL0600X": "Long-Term Care Clinical Nurse Specialist",
- "364SM0705X": "Medical-Surgical Clinical Nurse Specialist",
- "364SN0000X": "Neonatal Clinical Nurse Specialist",
- "364SN0800X": "Neuroscience Clinical Nurse Specialist",
- "364SP0200X": "Pediatric Clinical Nurse Specialist",
- "364SP0807X": "Child & Adolescent Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP0808X": "Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP0809X": "Adult Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP0810X": "Child & Family Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP0811X": "Chronically Ill Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP0812X": "Community Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP0813X": "Geropsychiatric Psychiatric/Mental Health Clinical Nurse Specialist",
- "364SP1700X": "Perinatal Clinical Nurse Specialist",
- "364SP2800X": "Perioperative Clinical Nurse Specialist",
- "364SR0400X": "Rehabilitation Clinical Nurse Specialist",
- "364SS0200X": "School Clinical Nurse Specialist",
- "364ST0500X": "Transplantation Clinical Nurse Specialist",
- "364SW0102X": "Women's Health Clinical Nurse Specialist",
- "364SX0106X": "Occupational Health Clinical Nurse Specialist",
- "364SX0200X": "Oncology Clinical Nurse Specialist",
- "364SX0204X": "Pediatric Oncology Clinical Nurse Specialist",
- "367500000X": "Certified Registered Nurse Anesthetist",
- "367A00000X": "Advanced Practice Midwife",
- "367H00000X": "Anesthesiologist Assistant",
- "372500000X": "Chore Provider",
- "372600000X": "Adult Companion",
- "373H00000X": "Day Training/Habilitation Specialist",
- "374700000X": "Technician",
- "3747A0650X": "Attendant Care Provider",
- "3747P1801X": "Personal Care Attendant",
- "374J00000X": "Doula",
- "374K00000X": "Religious Nonmedical Practitioner",
- "374T00000X": "Religious Nonmedical Nursing Personnel",
- "374U00000X": "Home Health Aide",
- "376G00000X": "Nursing Home Administrator",
- "376J00000X": "Homemaker",
- "376K00000X": "Nurse's Aide",
- "385H00000X": "Respite Care",
- "385HR2050X": "Respite Care Camp",
- "385HR2055X": "Child Mental Illness Respite Care",
- "385HR2060X": "Child Intellectual and/or Developmental Disabilities Respite Care",
- "385HR2065X": "Child Physical Disabilities Respite Care",
- "390200000X": "Student in an Organized Health Care Education/Training Program",
- "405300000X": "Prevention Professional"
-}
diff --git a/frontend/src/app/services/medical-sources-filter.service.spec.ts b/frontend/src/app/services/medical-sources-filter.service.spec.ts
new file mode 100644
index 00000000..4f72165e
--- /dev/null
+++ b/frontend/src/app/services/medical-sources-filter.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { MedicalSourcesFilterService } from './medical-sources-filter.service';
+
+describe('MedicalSourcesFilterService', () => {
+ let service: MedicalSourcesFilterService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(MedicalSourcesFilterService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/services/medical-sources-filter.service.ts b/frontend/src/app/services/medical-sources-filter.service.ts
new file mode 100644
index 00000000..cf870446
--- /dev/null
+++ b/frontend/src/app/services/medical-sources-filter.service.ts
@@ -0,0 +1,168 @@
+import { Injectable } from '@angular/core';
+import {FormBuilder, FormControl, FormGroup} from '@angular/forms';
+
+
+export class MedicalSourcesFilter {
+ searchAfter: string | string[] = '';
+ query: string;
+ platformTypes: string[] = [];
+ categories: string[] = [];
+ showHidden: boolean = false;
+
+ fields: string[] = []; //specify the fields to return. if null or empty list, return all.
+}
+
+
+@Injectable({
+ providedIn: 'root'
+})
+export class MedicalSourcesFilterService {
+ filterForm = this.formBuilder.group({
+ searchAfter: [''],
+ query: [''],
+ platformTypes: this.formBuilder.group({}),
+ categories: this.formBuilder.group({}),
+ showHidden: [false],
+ })
+
+ constructor(private formBuilder: FormBuilder) { }
+
+ //parse angular query string parameters
+ parseQueryParams(queryParams: {[name:string]:string}){
+
+ var updateData: {
+ searchAfter?: string,
+ query?: string,
+ platformTypes?: {},
+ categories?: {},
+ showHidden?: boolean,
+ } = {};
+
+ if(queryParams['searchAfter']){
+ updateData.searchAfter = queryParams['searchAfter']
+ }
+ if(queryParams['query']){
+ updateData.query = queryParams['query']
+ }
+
+ if(queryParams['platformTypes']){
+ updateData.platformTypes = updateData.platformTypes ? updateData.platformTypes : {};
+ for(let platformType of queryParams['platformTypes']?.split(',')){
+ updateData.platformTypes[platformType] = true;
+ }
+ }
+ if(queryParams['categories']){
+ updateData.categories = updateData.categories ? updateData.categories : {};
+ for(let category of queryParams['categories']?.split(',')){
+ updateData.categories[category] = true;
+ }
+ }
+ if(queryParams['showHidden']){
+ updateData.showHidden = queryParams['showHidden'] == 'true';
+ }
+
+
+ //ensure that checkbox list values exist before trying to "patch" them in.
+ if(updateData.platformTypes){
+ var currentFileTypes = this.filterForm.get('platformTypes').value;
+ Object.keys(updateData.platformTypes).forEach((bucketKey) => {
+ if(!currentFileTypes.hasOwnProperty(bucketKey)){
+ (this.filterForm.get('platformTypes') as FormGroup).addControl(bucketKey, new FormControl(false))
+ }
+ })
+ }
+ if(updateData.categories){
+ Object.keys(updateData.categories).forEach((bucketKey) => {
+ if(!this.filterForm.get('categories').get(bucketKey)){
+ (this.filterForm.get('categories') as FormGroup).addControl(bucketKey, new FormControl(false))
+ }
+ })
+ }
+
+ return updateData;
+ }
+
+ toQueryParams() : {[name:string]:string} {
+ var form = this.filterForm.value;
+
+ var queryParams = {};
+
+ // if(form.searchAfter){
+ // var searchAfter = [];
+ // Object.keys(form.searchAfter).forEach((key) => {
+ // if (form.searchAfter[key]) {
+ // searchAfter.push(key);
+ // }
+ // })
+ //
+ // queryParams['searchAfter'] = searchAfter.join(',');
+ // }
+ if(form.query){
+ queryParams['query'] = form.query
+ }
+
+ if(form.platformTypes && Object.keys(form.platformTypes).length){
+ var platformTypes = [];
+ Object.keys(form.platformTypes).forEach((key) => {
+ if (form.platformTypes[key]) {
+ platformTypes.push(key);
+ }
+ })
+
+ queryParams['platformTypes'] = platformTypes.join(',');
+ }
+
+ if(form.categories && Object.keys(form.categories).length){
+ var categories = [];
+ Object.keys(form.categories).forEach((key) => {
+ if (form.categories[key]) {
+ categories.push(key);
+ }
+ })
+ queryParams['categories'] = categories.join(',');
+ }
+
+ if(form.showHidden){
+ queryParams['showHidden'] = form.showHidden.toString();
+ }
+
+
+ return queryParams;
+ }
+
+ toMedicalSourcesFilter(form): MedicalSourcesFilter {
+
+ var medicalSourcesFilter = new MedicalSourcesFilter();
+
+ if(form.searchAfter){
+ medicalSourcesFilter.searchAfter = form.searchAfter
+ }
+ if(form.query){
+ medicalSourcesFilter.query = form.query;
+ }
+
+ if(form.platformTypes){
+ medicalSourcesFilter.platformTypes = [];
+ Object.keys(form.platformTypes).forEach((key) => {
+ if (form.platformTypes[key]) {
+ medicalSourcesFilter.platformTypes.push(key);
+ }
+ })
+ }
+ if(form.categories){
+ medicalSourcesFilter.categories = [];
+ Object.keys(form.categories).forEach((key) => {
+ if (form.categories[key]) {
+ medicalSourcesFilter.categories.push(key);
+ }
+ })
+ }
+
+ if (form.showHidden) {
+ medicalSourcesFilter.showHidden = form.showHidden;
+ }
+
+ return medicalSourcesFilter
+ }
+
+}
diff --git a/frontend/src/custom.scss b/frontend/src/custom.scss
index 6b85f6ea..ea8fe738 100644
--- a/frontend/src/custom.scss
+++ b/frontend/src/custom.scss
@@ -214,3 +214,23 @@ app-nlm-typeahead {
border-radius: 10px;
}
}
+
+//Medical Source Filter
+.category-label {
+ font-weight: 400
+}
+
+app-medical-sources-filter > .az-content-left-components {
+ overflow: hidden;
+ -webkit-transition: width 0.2s ease-in-out;
+ -moz-transition: width 0.2s ease-in-out;
+ -o-transition: width 0.2s ease-in-out;
+ transition: width 0.2s ease-in-out;
+}
+app-medical-sources-filter > .az-content-left-components:hover{
+ width: 100%;
+ -webkit-transition: width 0.2s ease-in-out;
+ -moz-transition: width 0.2s ease-in-out;
+ -o-transition: width 0.2s ease-in-out;
+ transition: width 0.2s ease-in-out;
+}