Skip to main content
Glama
address.ts6.96 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { WithId } from '@medplum/core'; import { formatAddress } from '@medplum/core'; import type { Address, Resource, ResourceType, SearchParameter } from '@medplum/fhirtypes'; import type { Pool, PoolClient } from 'pg'; import { DeleteQuery } from '../sql'; import type { LookupTableRow } from './lookuptable'; import { LookupTable } from './lookuptable'; export interface AddressTableRow extends LookupTableRow { address: string | undefined; city: string | undefined; country: string | undefined; postalCode: string | undefined; state: string | undefined; use: string | undefined; } /** * The AddressTable class is used to index and search Address properties. * Each Address is represented as a separate row in the "Address" table. */ export class AddressTable extends LookupTable { private static readonly resourceTypes = [ 'Patient', 'Person', 'Practitioner', 'RelatedPerson', 'InsurancePlan', 'Location', 'Organization', ] as const; private static readonly resourceTypeSet = new Set(this.resourceTypes); private static hasAddress(resourceType: ResourceType): resourceType is (typeof AddressTable.resourceTypes)[number] { return AddressTable.resourceTypeSet.has(resourceType as any); } private static readonly knownParams: Set<string> = new Set<string>([ 'individual-address', 'individual-address-city', 'individual-address-country', 'individual-address-postalcode', 'individual-address-state', 'individual-address-use', 'InsurancePlan-address', 'InsurancePlan-address-city', 'InsurancePlan-address-country', 'InsurancePlan-address-postalcode', 'InsurancePlan-address-state', 'InsurancePlan-address-use', 'Location-address', 'Location-address-city', 'Location-address-country', 'Location-address-postalcode', 'Location-address-state', 'Location-address-use', 'Organization-address', 'Organization-address-city', 'Organization-address-country', 'Organization-address-postalcode', 'Organization-address-state', 'Organization-address-use', ]); /** * Returns the table name. * @returns The table name. */ getTableName(): string { return 'Address'; } /** * Returns the column name for the address. * * Input | Output * --------------------+----------- * address | address * address-city | city * address-country | country * address-postalcode | postalCode * address-state | state * addrses-use | use * @param code - The search parameter code. * @returns The column name. */ getColumnName(code: string): string { if (code === 'address') { return 'address'; } if (code === 'address-postalcode') { return 'postalCode'; } return code.replace('address-', ''); } /** * Returns true if the search parameter is an Address parameter. * @param searchParam - The search parameter. * @returns True if the search parameter is an Address parameter. */ isIndexed(searchParam: SearchParameter): boolean { return AddressTable.knownParams.has(searchParam.id as string); } extractValues(result: AddressTableRow[], resource: WithId<Resource>): void { const addresses = this.getIncomingAddresses(resource); if (!Array.isArray(addresses)) { return; } for (const address of addresses) { const extracted = { resourceId: resource.id, // logical OR coalesce to ensure that empty strings are inserted as NULL address: formatAddress(address) || undefined, // formatAddress can return the empty string city: address.city?.trim() || undefined, country: address.country?.trim() || undefined, postalCode: address.postalCode?.trim() || undefined, state: address.state?.trim() || undefined, use: address.use?.trim() || undefined, }; if ( (extracted.address || extracted.city || extracted.country || extracted.postalCode || extracted.state || extracted.use) && !result.some( (a) => a.resourceId === extracted.resourceId && a.address === extracted.address && a.city === extracted.city && a.country === extracted.country && a.postalCode === extracted.postalCode && a.state === extracted.state && a.use === extracted.use ) ) { result.push(extracted); } } } async batchIndexResources<T extends Resource>( client: PoolClient, resources: WithId<T>[], create: boolean, resourceBatchSize?: number ): Promise<void> { if (!resources[0] || !AddressTable.hasAddress(resources[0].resourceType)) { return; } await super.batchIndexResources(client, resources, create, resourceBatchSize); } private getIncomingAddresses(resource: Resource): Address[] | undefined { if (!AddressTable.hasAddress(resource.resourceType)) { return undefined; } let addresses: (Address | undefined | null)[] | undefined; switch (resource.resourceType) { case 'Patient': case 'Person': case 'Practitioner': case 'RelatedPerson': case 'Organization': addresses = resource.address; break; case 'InsurancePlan': addresses = resource.contact?.map((contact) => contact.address); break; case 'Location': addresses = resource.address ? [resource.address] : undefined; break; default: resource.resourceType satisfies never; return undefined; } return addresses?.filter((a) => !!a); } /** * Deletes the resource from the lookup table. * @param client - The database client. * @param resource - The resource to delete. */ async deleteValuesForResource(client: Pool | PoolClient, resource: Resource): Promise<void> { if (!AddressTable.hasAddress(resource.resourceType)) { return; } const tableName = this.getTableName(); const resourceId = resource.id; await new DeleteQuery(tableName).where('resourceId', '=', resourceId).execute(client); } /** * Purges resources of the specified type that were last updated before the specified date. * This is only available to the system and super admin accounts. * @param client - The database client. * @param resourceType - The FHIR resource type. * @param before - The date before which resources should be purged. */ async purgeValuesBefore(client: Pool | PoolClient, resourceType: ResourceType, before: string): Promise<void> { if (!AddressTable.hasAddress(resourceType)) { return; } await super.purgeValuesBefore(client, resourceType, before); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server