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

EA-207: Implement an API for Fetching Visits with Notes and Diagnosis #250

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

jayasanka-sack
Copy link
Member

@jayasanka-sack jayasanka-sack commented Oct 24, 2024

Ticket: https://openmrs.atlassian.net/browse/EA-207

We needed to create a new API that retrieves visits along with their associated notes and diagnosis to optimize the current approach. The existing API call we are using to fetch past visits is expensive and returns more data than necessary for the initial UI rendering. The goal is to retrieve only the relevant information (visits, notes, diagnosis) to improve performance and load times.

@mogoodrich
Copy link
Member

@jayasanka-sack can you write up an EMR-API ticket in Jira describing the use cases on what this PR is intended to accomplish? thanks, fyi @mseaton

@jayasanka-sack jayasanka-sack changed the title Implement an API for Fetching Visits with Notes and Diagnosis WIP: [Do not review] Implement an API for Fetching Visits with Notes and Diagnosis Oct 24, 2024
@jayasanka-sack
Copy link
Member Author

jayasanka-sack commented Oct 24, 2024

Hey @mogoodrich The ticket is: https://openmrs.atlassian.net/browse/EA-207

This PR is still under WIP. I'll let you know when it is ready for review.

@jayasanka-sack jayasanka-sack changed the title WIP: [Do not review] Implement an API for Fetching Visits with Notes and Diagnosis WIP: [Do not review] EA-207: Implement an API for Fetching Visits with Notes and Diagnosis Oct 24, 2024
@jayasanka-sack jayasanka-sack changed the title WIP: [Do not review] EA-207: Implement an API for Fetching Visits with Notes and Diagnosis WIP: EA-207: Implement an API for Fetching Visits with Notes and Diagnosis Nov 21, 2024
@jayasanka-sack jayasanka-sack changed the title WIP: EA-207: Implement an API for Fetching Visits with Notes and Diagnosis EA-207: Implement an API for Fetching Visits with Notes and Diagnosis Nov 28, 2024
@jayasanka-sack jayasanka-sack marked this pull request as ready for review November 28, 2024 06:06
Copy link
Member

@mogoodrich mogoodrich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jayasanka-sack ! Good to see we are adding methods to the EMR API to support performant queries in O3.

See my comments and let me know if you have questions... also, can you add a link to this PR to the ticket?

Also, it looks like this is really just returning "Visits With Diagnoses" (not "Visits with Notes and Diagnsoes")... which is fine, just wanted to confirm it is what you were looking for.

also fyi @mseaton


public List<VisitWithDiagnoses> getVisitsByPatientId(Patient patient, int startIndex, int limit) {

String visitNoteEncounterTypeUuid = "d7151f82-c1f3-4152-a605-2f9ea7414a79";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hardcoding the uuid here, we should be fetching this based on the encounter that has the "emr.visitNoteEncounterType" metadata mapping, see:

https://github.com/openmrs/openmrs-module-emrapi/blob/master/api/src/main/java/org/openmrs/module/emrapi/EmrApiProperties.java#L93

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And actually, why are we including this subquery at all? Do we only want to include visits that have visit notes (this seems wrong)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We’re including this to get only note encounters with the visit, but we also need visits that don’t have encounters. I’ve updated the query with a left join and modified the test accordingly.

@Autowired
private SessionFactory sessionFactory;

public List<VisitWithDiagnoses> getVisitsByPatientId(Patient patient, int startIndex, int limit) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's call this getVisitsWithDiagnosesByPatient to be clear what it returns. Also note that we don't/shouldn't have the "Id" at the end, because this takes a Patient, not a patient Id.

* @param patientUuid the UUID of the patient
* @return a list of visits that has note encounters and diagnoses
*/
List<VisitWithDiagnoses> getVisitsByPatientId(String patientUuid, int startIndex, int limit);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here, let's make this "getVisitsWithDiagnosesByPatient" (add "WithDiagnoses" and remove "Id")

VisitDAO visitDAO;

@Override
public List<VisitWithDiagnoses> getVisitsByPatientId(String patientUuid, int startIndex, int limit) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's have this take a Patient patient, not a String patientUuid (see my further comments below)

omod/pom.xml Outdated
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not necessarily totally opposed to this, but what Junit functionality is this added that we don't already have available in this module?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jupiter is JUnit 5's base engine, so I expect this is to be able to write tests in JUnit 5?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We either should choose to use Junit 4 or Junit 5 for the entire module. I don't want us to have to try to support both in one module, there are too many annoying gotchas.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if it's problematic to support both JUnit 4 and JUnit 5 in a single module, let's just go with JUnit 4 until we feel motivated to update all the tests.

public ResponseEntity<?> getVisitsByPatientId(
HttpServletRequest request,
HttpServletResponse response,
@PathVariable String patientUuid) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so normally we do something like this:

@RequestParam("patient") Patient patient

...and there is a converter behind the scenes that handles the conversion from uuid -> Patient (and throws an Exception if no patient found). Did you/can you test if this "automagically" works:

@PathVariable Patient patientUuid

(Then the underlying service method can just be getVisitsByPatient(Patient, int, int)

Copy link
Member Author

@jayasanka-sack jayasanka-sack Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m guessing it needs to be called with ?patient={uuid} but it’s returning a 500 with no body, and I’m having a hard time debugging it. Can you point me to where the conversion happens? I noticed a couple of other endpoints which use the same, but I couldn’t find any tests for them or any part of the frontend that calls them. I tried @PathVariable Patient patientUuid as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it's part of the UI framework, which is almost certainly not the right place for it. I think you could also do @PathVariable("patientUuid") Patient patient with the right converter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m still stuck on this part. I tried using both the path variable and the request parameter, but it still throws a 500 error without any message. Any ideas on how I can debug this?

By the way, this code has been used here:

public SimpleObject ensureActiveVisit(@RequestParam("patient") Patient patient,

Copy link
Member

@ibacher ibacher Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try adding the UIFramework module? The problem is that without the converter, Spring doesn't know how to change the value it gets (a String) into the thing you want (a Patient object).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No real objection, but also no need to get bogged down on this. Just accept a String parameter (which could be a uuid or primary key id), and convert it to a patient in the controller...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, so things like the ActiveVisitController have been dependent on UI Framework module all along, we just never realized it because we've generally always been running EMR API and UI Framework together?

No strong objection to move them... I think I'd have slight preference for moving them to EMR API instead of REST Web Services, but haven't thought it all through. We also could potentially add them to Core? (Though of course the adoption time would be slower that way).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it looks like REST WS may have it's own converters, fwiw...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REST has it's own Converter concept, but it's actually distinct from the Spring converters, I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also could potentially add them to Core? (Though of course the adoption time would be slower that way)

Yeah, I'd like to migrate them to core eventually, but we just released 2.7.0, so it's not the best time to do that. Easy enough to add to a module and then add an @OpenmrsProfile to exclude them once they're available in core.

VisitWithDiagnosesService visitWithDiagnosesService;

@RequestMapping(method = RequestMethod.GET, value = "/rest/**/emrapi/patient/{patientUuid}/visit")
public ResponseEntity<?> getVisitsByPatientId(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make this "getVisitsWithDiagnosesByPatient"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the endpoint should not be /.../visit, but should reflect that it is a /.../visitWithDiagnoses or something

<!-- <metadatamapping_metadata_term_mapping metadata_term_mapping_id="3945" metadata_source_id="2134"-->
<!-- creator="1" date_created="2023-10-20 12:38:58.0" uuid="111bbad0-d565-11e3-9c1a-0800200c9a665"-->
<!-- retired="false" code="emr.visitNoteEncounterType" name="emr.visitNoteEncounterType"-->
<!-- metadata_uuid="06087e80-d565-11e3-9c1a-0800200c9a66" metadata_class="org.openmrs.EncounterType"/>-->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is commented out, let's just not include it?

@@ -0,0 +1,35 @@
<?xml version='1.0' encoding='UTF-8'?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this class being used, or is everything handled in the other "pastVisitSetup" class below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both files are used in tests in both modules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the distinction between them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the organization is a little weird. It looks like there may have been some convention for all the metadata to go into a file like this and the data into a file like the other one, but both of them mix data and metadata, so it's confusing why we need the two.


import java.util.List;

public interface VisitDAO {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may want to call with EmrApiVisitDAO to distinguish it from the Core one? Thoughts @mseaton @ibacher ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need an entirely new DAO class we have to wire up and maintain? Can't we just move to having one DAO for the entire module?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm fine with using the existing "EmrApiDAO" for this... there are some other DAOs already in the EMR API module with obsolete names ("EmrEncounterDAO"), but we could move away from these in favor of a single DAO.


import java.util.List;

public interface VisitWithDiagnosesService {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this EmrApiVisitService (and we can use it for other Visit-related services we add to EMR API)? Thoughts @mseaton @ibacher ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a very good idea!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Let's move towards having fewer distinct DAOs and Services in this module

import java.util.Set;
import java.util.stream.Collectors;

@Repository
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have never seen this annotation actually used in this module before. Are we sure this works? I'd prefer we stick with existing conventions within the module for wiring things up, and then do a change to everything if we want to update to a new mechanism.

@Repository
public class HibernateVisitDAO implements VisitDAO {

@Autowired
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we do not autowire into a DAO AFAIK, but we use xml. There are definitely performance issues to using Autowired in services, and I'm not sure if those carry over to DAOs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch and 100% agree... it's annoying, but we need to manually wire services and daos using xml... there should be examples in this module and Core.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They likely don't, but I'd still favour XML over autowiring.


import java.util.List;

public interface VisitDAO {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need an entirely new DAO class we have to wire up and maintain? Can't we just move to having one DAO for the entire module?


import java.util.List;

public interface VisitWithDiagnosesService {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Let's move towards having fewer distinct DAOs and Services in this module

omod/pom.xml Outdated
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We either should choose to use Junit 4 or Junit 5 for the entire module. I don't want us to have to try to support both in one module, there are too many annoying gotchas.

VisitWithDiagnosesService visitWithDiagnosesService;

@RequestMapping(method = RequestMethod.GET, value = "/rest/**/emrapi/patient/{patientUuid}/visit")
public ResponseEntity<?> getVisitsByPatientId(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the endpoint should not be /.../visit, but should reflect that it is a /.../visitWithDiagnoses or something

@jayasanka-sack
Copy link
Member Author

Appreciate the feedback @mogoodrich and @mseaton! I’ll take a look and follow up with you.

@jayasanka-sack
Copy link
Member Author

I still have to address some comments. I'll get them fixed and get back to you.

Copy link
Member

@mogoodrich mogoodrich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, juts realized I had a couple "pending" comments that I never submitted.

@Repository
public class HibernateVisitDAO implements VisitDAO {

@Autowired
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch and 100% agree... it's annoying, but we need to manually wire services and daos using xml... there should be examples in this module and Core.


import java.util.List;

public interface VisitDAO {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm fine with using the existing "EmrApiDAO" for this... there are some other DAOs already in the EMR API module with obsolete names ("EmrEncounterDAO"), but we could move away from these in favor of a single DAO.

Copy link
Member

@mogoodrich mogoodrich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jayasanka-sack , getting close! See my updated thoughts/questions.

String visitNoteEncounterTypeUuid = emrApiProperties.getVisitNoteEncounterType().getUuid();

String hqlVisit="SELECT DISTINCT v FROM Visit v " +
"LEFT JOIN FETCH v.encounters enc " +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I wasn't aware of this FETCH syntax.... this is saying when fetching the encounters associated with the visit, limit to those which are notes? ie, although this query only returns visits, you can fetch the encounters via visit.encounters, and those will be limited to notes? @jayasanka-sack

Copy link
Member Author

@jayasanka-sack jayasanka-sack Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you’re right!

P.S.
I used the FETCH syntax to load encounters eagerly instead of lazily.

@@ -0,0 +1,35 @@
<?xml version='1.0' encoding='UTF-8'?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the distinction between them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants