Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MockClient returns incorrect data when searching with token parameters due to the way matchesTokenFilter operates #4509

Closed
pamella opened this issue May 3, 2024 · 3 comments · Fixed by #4589
Labels
bug Something isn't working fhir-datastore Related to the FHIR datastore, includes API and FHIR operations

Comments

@pamella
Copy link
Collaborator

pamella commented May 3, 2024

Issue

MockClient returns incorrect data when searching with token parameters because the current implementation of the matchesTokenFilter function does not support exact match searches, as specified in the FHIR documentation.

Problem

In the original implementation, the matchesTokenFilter function uses the includes method to check if the token value is included in the search parameter. This implementation does not support exact match searches, as specified in the FHIR documentation.

Taking into account that the MockClient uses the MemoryRepository which uses the matchesSearchRequest function to filter the resources, we can get incorrect results when searching with token parameters. For example, if mockClient.upsertResource is called with an observation with code 12345-6 and then another mockClient.upsertResource is called with an observation with code 1234, the first will be replaced by the second one because MemoryRepository will be used and it will consider they are the same resource because the matchesSearchRequest function will return true when it should return false.

Examples

Example 1

When searching for a patient with the identifier value 1234567890, the matchesTokenFilter function will return true for any identifier value that includes 1234567890, such as 1234, 12345, etc.

When we add the following test to packages/core/src/search/match.test.ts, it fails when it should pass:

➡️ Click to expand and see the code
test('[Token] Datatype: Identifier', () => {
  const identifier = '1234567890';
  const identifierSubstring = identifier.substring(0, 4);
  const resource: Patient = {
    resourceType: 'Patient',
    identifier: [
      {
        system: 'http:https://example.com',
        value: identifier,
      },
      {
        system: 'http:https://test.com',
        value: identifier,
      },
      {
        system: 'http:https://test.com',
        value: 'foo',
      },
    ],
  };

  expect(
    matchesSearchRequest(resource, {
      resourceType: 'Patient',
      filters: [
        { code: 'identifier', operator: Operator.EQUALS, value: 'http:https://example.com|' + identifierSubstring },
      ],
    })
  ).toBe(false); // FAIL! It currently returns true, but it should return false

  expect(
    matchesSearchRequest(resource, {
      resourceType: 'Patient',
      filters: [{ code: 'identifier', operator: Operator.EQUALS, value: identifierSubstring }],
    })
  ).toBe(false); // FAIL! It currently returns true, but it should return false
});

Example 2

When searching for an observation with the code value 12345-6, the matchesTokenFilter function will return true for any code value that includes 12345-6, such as 12345, 12345-, etc.

When we add the following test to packages/core/src/search/match.test.ts, it fails when it should pass:

➡️ Click to expand and see the code
test('[Token] Datatype: CodeableConcept', () => {
  const identifier = '12345-6';
  const identifierSubstring = identifier.substring(0, 4);
  const resource: Observation = {
    resourceType: 'Observation',
    status: 'final',
    code: {
      coding: [
        {
          system: 'http:https://example.com',
          code: identifier,
        },
        {
          system: 'http:https://test.com',
          code: identifier,
        },
        {
          system: 'http:https://test.com',
          code: 'foo',
        },
      ],
      text: 'test',
    },
  };

  expect(
    matchesSearchRequest(resource, {
      resourceType: 'Observation',
      filters: [{ code: 'code', operator: Operator.EQUALS, value: 'http:https://example.com|' + identifierSubstring }],
    })
  ).toBe(false); // FAIL! It currently returns true, but it should return false

  expect(
    matchesSearchRequest(resource, {
      resourceType: 'Observation',
      filters: [{ code: 'code', operator: Operator.EQUALS, value: identifierSubstring }],
    })
  ).toBe(false); // FAIL! It currently returns true, but it should return false
});

Proposed solution

That being said, I thought about a partial solution that would allow us to support exact match searches for token parameters. I say partial because it would address the identifier and code types. Here is a code snippet:

➡️ Click to expand and see the code
function matchesStringFilter(
  resource: Resource,
  filter: Filter,
  searchParam: SearchParameter,
  asToken?: boolean
): boolean {
  const details = getSearchParameterDetails(resource.resourceType, searchParam);
  const searchParamElementType = details.elementDefinitions?.[0].type?.[0].code;
  const resourceValues = evalFhirPath(searchParam.expression as string, resource);
  const filterValues = filter.value.split(',');
  const negated = isNegated(filter.operator);
  for (const resourceValue of resourceValues) {
    for (const filterValue of filterValues) {
      let match;
      if (searchParamElementType === PropertyType.Identifier) {
        match = matchesTokenIdentifierValue(resourceValue as Identifier, filter.operator, filterValue);
      } else if (searchParamElementType === PropertyType.CodeableConcept) {
        match = matchesTokenCodeableConceptValue(resourceValue as Coding, filter.operator, filterValue);
      } else {
        match = matchesStringValue(resourceValue, filter.operator, filterValue, asToken);
      }
      if (match) {
        return !negated;
      }
    }
  }
  // If "not equals" and no matches, then return true
  // If "equals" and no matches, then return false
  return negated;
}

function matchesTokenCodeableConceptValue(
  resourceValue: CodeableConcept,
  operator: Operator,
  filterValue: string
): boolean {
  if (filterValue.includes('|')) {
    const [system, code] = filterValue.split('|');
    return (
      resourceValue.coding?.some(
        (coding) =>
          coding.system?.toLowerCase() === system.toLowerCase() && coding.code?.toLowerCase() === code.toLowerCase()
      ) ?? false
    );
  }

  return (
    resourceValue.text?.toLowerCase() === filterValue.toLowerCase() ||
    (resourceValue.coding?.some((coding) => coding.code?.toLowerCase() === filterValue.toLowerCase()) ?? false)
  );
}

I was working on this solution, however I wonder if:

  • It would be enough to have a partial solution because I understand there are a lot of nuances in FHIR standards about search such as operators and modifiers.
  • It would be a overkill to implement a full solution considering (I suppose) that the main usage is for testing purposes.
  • It might be a breaking change to other matches* functions that are using the matchesStringFilter function. For example, matchesAccessPolicyResourcePolicy and matchesCriteria.
  • It might be a breaking change to customers that are already using the MockClient class.

So I would like to know your opinion about all of this.

Please let me know if you have any questions or need further information. I am looking forward to your feedback. Thank you!

@rahul1
Copy link
Member

rahul1 commented May 6, 2024

Thanks @pamella . This is related to #1237

@rahul1 rahul1 added bug Something isn't working fhir-datastore Related to the FHIR datastore, includes API and FHIR operations labels May 6, 2024
@codyebberson
Copy link
Member

Thanks @pamella - I added comments to your draft PR. This all looks good to me 👍 Thank you 🙏

github-merge-queue bot pushed a commit that referenced this issue May 6, 2024
… in `matchesTokenFilter` (#4516)

* Add initial token match support of Identifier and CodeableConcept

* Remove console.log

* Fixed observation test

---------

Co-authored-by: Cody Ebberson <[email protected]>
@rahul1
Copy link
Member

rahul1 commented May 6, 2024

Thanks @pamella

@rahul1 rahul1 closed this as completed May 6, 2024
@reshmakh reshmakh added this to the May 31st, 2024 milestone May 12, 2024
medplumbot added a commit that referenced this issue May 25, 2024
Fix age display in PatientSummary (#4484)Fixes #4471 - DetectedIssue.status valueset and search param (#4483)
Qualify columns with table name in generated SQL (#4487)
fix(migrations): only take lock if migrating (#4490)
Document updating profiles (#4402)
Run expand tests against old and new (#4503)
Adding OpenCareHub support post (#4504)
[Medplum Provider app] Various fixes and touchups (#4500)
ci(agent): add workflow for building agent outside of a release (#4512)
feat(agent): `Agent/$reload-config` operation (#4457)
Remove broken links to Foo Provider (#4518)
Dependency upgrades 2024-05-06 (#4515)
PatientSummary and provider app tweaks (#4521)
Fixes #4509 - Improve exact match search support for token parameters in `matchesTokenFilter` (#4516)
Link to new Demo Applications (#4514)
Clarify that autobatching only applies to `GET` requests (#4479)
Added missing useEffect dependency in chat demo (#4527)
fix: allows CORS for `keyvalue` API (#4476)
Fixes #4508 - MeasureReport-subject search param backport (#4530)
Fix CLI update-server version flag (#4534)
Patient summary appointments and encounters links (#4524)
Remove unused DB columns (#4532)
fix(agent): unwrap response for `$reload-config` by id (#4542)
`PatientSummary` Problem List uses US Core profile (#4535)
fix(cli): always exit with exit code 1 after error occurs during command (#4536)
Add failing test about validating nested extensions (#4548)
Add error message when `cli` fails on login (#4507)
Remove functions moved to core (#4547)
fix: AttachmentDisplay use uncached url for download link (#4501)
feat(agent): respect `Agent.status` and `Agent.channel.endpoint.status` being `off` (#4523)
CMS 1500 and Superbill (#4543)
Demo Bot: Agent Setup (#4555)
feat(Subscription): add `author` as a `SearchParameter` (#4540)
Dependency upgrades 2024-05-13 (#4544)
Full linked Project ordering in CodeSystem lookup (#4522)
Disable super admin refresh tokens (#4492)
Minor fixes for the agent setup bot (#4560)
docs(agent): document how logging works with `Bot` and `Agent` (#4563)
Split rate limits into two buckets (#4568)
Properly detect array elements (#4569)
Apply filter to ValueSet with expansion.contains (#4570)
More efficiently validate included concepts (#4573)
Dependency upgrades 2024-05-20 (#4574)
tweak(agent): add timezone in status `lastUpdated` time (#4564)
fix(client/keyvalue): set keyvalue content-type text (#4575)
Allow configuring server default rate limits (#4491)
feat(cli): add `token` command to get access token (#4579)
Updating device resources and videos (#4578)
fix(subscriptions): don't retry ws subs if sub is deleted (#4577)
Add support for 'pr' filter operation (#4584)
Super admin endpoint for database stats (#4443)
github-merge-queue bot pushed a commit that referenced this issue May 25, 2024
Fix age display in PatientSummary (#4484)Fixes #4471 - DetectedIssue.status valueset and search param (#4483)
Qualify columns with table name in generated SQL (#4487)
fix(migrations): only take lock if migrating (#4490)
Document updating profiles (#4402)
Run expand tests against old and new (#4503)
Adding OpenCareHub support post (#4504)
[Medplum Provider app] Various fixes and touchups (#4500)
ci(agent): add workflow for building agent outside of a release (#4512)
feat(agent): `Agent/$reload-config` operation (#4457)
Remove broken links to Foo Provider (#4518)
Dependency upgrades 2024-05-06 (#4515)
PatientSummary and provider app tweaks (#4521)
Fixes #4509 - Improve exact match search support for token parameters in `matchesTokenFilter` (#4516)
Link to new Demo Applications (#4514)
Clarify that autobatching only applies to `GET` requests (#4479)
Added missing useEffect dependency in chat demo (#4527)
fix: allows CORS for `keyvalue` API (#4476)
Fixes #4508 - MeasureReport-subject search param backport (#4530)
Fix CLI update-server version flag (#4534)
Patient summary appointments and encounters links (#4524)
Remove unused DB columns (#4532)
fix(agent): unwrap response for `$reload-config` by id (#4542)
`PatientSummary` Problem List uses US Core profile (#4535)
fix(cli): always exit with exit code 1 after error occurs during command (#4536)
Add failing test about validating nested extensions (#4548)
Add error message when `cli` fails on login (#4507)
Remove functions moved to core (#4547)
fix: AttachmentDisplay use uncached url for download link (#4501)
feat(agent): respect `Agent.status` and `Agent.channel.endpoint.status` being `off` (#4523)
CMS 1500 and Superbill (#4543)
Demo Bot: Agent Setup (#4555)
feat(Subscription): add `author` as a `SearchParameter` (#4540)
Dependency upgrades 2024-05-13 (#4544)
Full linked Project ordering in CodeSystem lookup (#4522)
Disable super admin refresh tokens (#4492)
Minor fixes for the agent setup bot (#4560)
docs(agent): document how logging works with `Bot` and `Agent` (#4563)
Split rate limits into two buckets (#4568)
Properly detect array elements (#4569)
Apply filter to ValueSet with expansion.contains (#4570)
More efficiently validate included concepts (#4573)
Dependency upgrades 2024-05-20 (#4574)
tweak(agent): add timezone in status `lastUpdated` time (#4564)
fix(client/keyvalue): set keyvalue content-type text (#4575)
Allow configuring server default rate limits (#4491)
feat(cli): add `token` command to get access token (#4579)
Updating device resources and videos (#4578)
fix(subscriptions): don't retry ws subs if sub is deleted (#4577)
Add support for 'pr' filter operation (#4584)
Super admin endpoint for database stats (#4443)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fhir-datastore Related to the FHIR datastore, includes API and FHIR operations
Projects
Status: No status
Development

Successfully merging a pull request may close this issue.

4 participants