Skip to content

Conversation

@angshu
Copy link
Member

@angshu angshu commented Nov 23, 2025

  • added column - order_id (ref FK to order.order_id)
  • added column - conclusion
  • added status enums - amended, cancelled

API

  • updated translator to map order ref
  • introduced new OrderReferenceTranslator that looks up the order
  • added tests

see https://issues.openmrs.org/browse/FM2-675

Checklist: I completed these to help reviewers :)

  • My IDE is configured to follow the code style of this project.

    No? Unsure? -> configure your IDE, format the code and add the changes with git add . && git commit --amend

  • I have added tests to cover my changes. (If you refactored
    existing code that was well tested you do not have to add tests)

  • I ran mvn clean package right before creating this pull request and
    added all formatting changes to my commit.

  • All new and existing tests passed.

  • My pull request is based on the latest changes of the master branch.

- added column - order_id (ref FK to order.order_id)
- added column - conclusion
- added status enums - amended, cancelled

API
- updated translator to map order ref
- introduced new ServiceRequestReferenceTranslator that looks up the order
- added tests
@codecov
Copy link

codecov bot commented Nov 23, 2025

Codecov Report

❌ Patch coverage is 60.97561% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.72%. Comparing base (c7f2ec2) to head (8ab503c).
⚠️ Report is 90 commits behind head on master.

Files with missing lines Patch % Lines
...translators/impl/OrderReferenceTranslatorImpl.java 50.00% 4 Missing and 5 partials ⚠️
...anslators/impl/DiagnosticReportTranslatorImpl.java 66.67% 2 Missing and 3 partials ⚠️
.../translators/impl/ReferenceHandlingTranslator.java 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #583      +/-   ##
============================================
+ Coverage     77.84%   78.72%   +0.87%     
+ Complexity     2683     2626      -57     
============================================
  Files           239      249      +10     
  Lines          7452     8167     +715     
  Branches        901     1079     +178     
============================================
+ Hits           5801     6429     +628     
- Misses         1115     1130      +15     
- Partials        536      608      +72     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

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

Thanks @angshu!

I'm not sure about naming of things here. This adds stuff about GenericServiceRequest, but in FHIR terms, the objects its operating on are all Requests (not every type of FHIR request, but a MedicationRequest has no relationship to a "ServiceRequest" in FHIR terminology). They are both really about "Orders" in OpenMRS terms, and should probably be configured that way.

SImilarly, "ServiceRequestReferenceTranslator" is really an "OrderReferenceTranslator". It would also be better if this "OrderReferenceTranslator" deferred to the existing "MedicationRequestReferenceTranslator" for MedicationRequests. There are too many interfaces in this module, but that's down to want to provide a high degree of overridability via implementations providing their own definitions of those, so it's best if we keep things defined in a single place.

Finally, it would be good if the tests for the translator better covered the set of edge-cases its meant to, mostly to ensure that it's functioning correctly (I think there's a subtle bug in the toOpenmrsType() implementation.

Comment on lines 106 to 109
Optional.ofNullable(fhirDiagnosticReport.getConclusion()).ifPresent(value -> diagnosticReport.setConclusion(value));
Optional.ofNullable(fhirDiagnosticReport.getOrder()).ifPresent((value -> {
diagnosticReport.addBasedOn(serviceRequestReferenceTranslator.toFhirResource(value));
}));
Copy link
Member

Choose a reason for hiding this comment

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

Using Optional in this way feels unidiomatic to me.

Copy link
Member Author

Choose a reason for hiding this comment

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

first one, will change. second one, wanted to guard against the serviceRequestTranslator not handling null. Yeah, the "ServiceRequestReferenceTranslatorImpl" does do a null check, but ToFhirTranslator.toFhirResource() really expects a notnull parameter.

if (order instanceof DrugOrder) {
return ReferenceHandlingTranslator.createDrugOrderReference((DrugOrder) order);
} else {
return new Reference().setReference(FhirConstants.SERVICE_REQUEST + "/" + order.getUuid())
Copy link
Member

Choose a reason for hiding this comment

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

It probably would've been more consistent to just use ReferenceHandlingTranslator.createOrderReference() and modify that as necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

thought about it. Didn't want to disturb that. Will change and update

Copy link
Member Author

Choose a reason for hiding this comment

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

there are 2 test cases which break and I am not sure if they should be asserted/tested the way they are written currently.

ObservationBasedOnReferenceTranslatorImplTest.toFhirType_shouldReturnNullForUnknownOrderType()

  • translator.toFhirResource(order) - does not check against the Order.OrderType - but rather checks against the java type of the order (Order, TestOrder, DrugOrder etc). So this test case is redundant really, if I change the implementation at ReferenceHandlingTranslator

ObservationBasedOnReferenceTranslatorImplTest.toFhirResource_shouldReturnNullIfOrderTypeIsNull()

  • same case here as well.

If the check against order.orderType is really what was expected, then the implementation in ObservationBasedOnReferenceTranslatorImpl should really override and check against the order.orderType

Let me know what you want me to do. I can remove or ignore the tests.

Copy link
Member Author

@angshu angshu Nov 27, 2025

Choose a reason for hiding this comment

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

Also not sure if - ReferenceHandlingTranslatorTest.shouldReturnNullForUnknownOrderSubclass() - was introduced intended to only handle Order as being Lab Orders (e.g path/hema/micro labs) or Medication Requests? We model orders of different types - surgical procedures, Physiological evaluations ...

If we put a check that order.orderType must be specified - that would break other test cases, but if thats never likely to happen - I can put that condition and try to fix the tests

whereas - ReferenceHandlingTranslatorTest.shouldReturnNullForRawOrder() - seems to guard against base Order

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 this was more a case of ensuring that we weren't accidentally translating order type (classes) that we didn't explicitly support since. Basically, anything descended from, e.g., TestOrder has, at a minimum, all of those fields that we mapped in ServiceRequest, but some other random descendant of Order may not. I don't know that keeping them is essential.

Optional<String> referenceType = getReferenceType(reference);
if (referenceType
.map(ref -> !(ref.equals(FhirConstants.SERVICE_REQUEST) || ref.equals(FhirConstants.MEDICATION_REQUEST)))
.orElse(false)) {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be:

Suggested change
.orElse(false)) {
.orElse(true)) {

Copy link
Member Author

Choose a reason for hiding this comment

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

good catch! will update

</not>
</preConditions>
<addColumn tableName="fhir_diagnostic_report">
<column name="conclusion" type="VARCHAR(512)"/>
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be somewhat better to just store this as a CLOB? Then we don't need to worry so much about the conclusion size.

Copy link
Member Author

Choose a reason for hiding this comment

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

Allocating too much VARCHAR /TEXT/CLOB for a column can lead to inefficient storage and a DB Server may overestimate the memory required for queries, potentially leading to excessive memory allocation. It's generally better to allocate a size that closely matches the expected data length to optimize performance and resource usage. We can always expand the size if required later. Would 1024 be enough?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, 1024 seems better. 512 characters requires things to be pretty terse.

@JoinColumn(name = "order_id", referencedColumnName = "order_id")
private Order order;

@Column(name = "conclusion")
Copy link
Member

Choose a reason for hiding this comment

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

length defaults to 255, so if we're using 512, we should specify that here.

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 deliberately didn't mention the size in case we want to change the db column size later. I can add - see related comment, if you would want little more capacity (e.g. 1024)

Copy link
Member

Choose a reason for hiding this comment

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

I suppose we don't really use the Hibernate metadata for anything, so it's probably fine.

Copy link
Member

Choose a reason for hiding this comment

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

@angshu
Copy link
Member Author

angshu commented Nov 28, 2025

Thanks @angshu!

I'm not sure about naming of things here. This adds stuff about GenericServiceRequest, but in FHIR terms, the objects its operating on are all Requests (not every type of FHIR request, but a MedicationRequest has no relationship to a "ServiceRequest" in FHIR terminology). They are both really about "Orders" in OpenMRS terms, and should probably be configured that way.

SImilarly, "ServiceRequestReferenceTranslator" is really an "OrderReferenceTranslator". It would also be better if this "OrderReferenceTranslator" deferred to the existing "MedicationRequestReferenceTranslator" for MedicationRequests. There are too many interfaces in this module, but that's down to want to provide a high degree of overridability via implementations providing their own definitions of those, so it's best if we keep things defined in a single place.

Finally, it would be good if the tests for the translator better covered the set of edge-cases its meant to, mostly to ensure that it's functioning correctly (I think there's a subtle bug in the toOpenmrsType() implementation.

👍 I wasn't happy with the naming and the way I had to implement. Also, I didn't want to change a lot of things there. Unfortunately, existing code doesn't make it easy to reuse code.

FhirServiceRequestServiceImpl and FhirServiceRequestDaoImpl is modeled over TestOrder. Not all implementations models ServiceRequest over TestOrder, and there are many Order/ServiceRequest types that should not be forced over TestOrder (e.g. Physiological Assessments, Psychological assessment, Surgical Procedure Orders). In OMRS 2.5, TestOrder itself is derived from ServiceOrder subclass of Order, and there's also ReferralOrder. All these map to ServiceRequests. DiagnosticReport.basedOn can also be a MedicationRequest - which maps to OpenMRS DrugOrder.

if we were to truly support the ideas here and model through ServiceRequest and MedicationRequest, then we really need to bring in flexibility of allowing implementers to map through OrderType and delegate to their own translations and mappings.

if we would to do this, it would require fair amount of refactoring. I can give it a shot, but it may take sometime due to my availability.

Let me know your thoughts.

@angshu
Copy link
Member Author

angshu commented Nov 28, 2025

I will, for now, just renamed the classes. However, re-using and changing the code in ReferenceHandlingTranslator will mean that any Order as a fallback will be a ServiceRequest reference, which for many wouldn't work - as the FhirServiceRequestServiceImpl will only understand TestOrder.

I am inclined to not use ReferenceHandlingTranslator for now, and live with the duplicates for now.

When fhir2 moves over to core 2.5, (currently 2.4.1), we probably can refactor to check against ReferralOrder, TestOrder, ServiceOrder, and Order hierarchy. Or maybe refactor the FhirServiceRequestServiceImpl to make it more generic (by using Order.OrderType) instead by providing extensions/hooks for implementations to override behavior.

@angshu
Copy link
Member Author

angshu commented Nov 28, 2025

I have pushed the code. Please check. Note, the test cases ignored in

  • ReferenceHandlingTranslatorTest
  • ObservationBasedOnReferenceTranslatorImplTest

This is due to reuse of the ReferenceHandlingTranslator.createOrderReference(Order). And as I mentioned above, can potentially problems for implementers as the fallback will always be a ServiceRequest reference, and the default FhirServiceRequestServiceImpl will not be able to resolve resource requests against that. You may argue that, with the current implementation everybody would have earlier modeled around TestOrder, so no harm done.

Let me know if you prefer this way, or just resolve the reference in the OrderReferenceTranslator.toFhirResource() with some code duplication. I will clean up the code and update PR @ibacher

@ibacher
Copy link
Member

ibacher commented Dec 8, 2025

Not all implementations models ServiceRequest over TestOrder, and there are many Order/ServiceRequest types that should not be forced over TestOrder (e.g. Physiological Assessments, Psychological assessment, Surgical Procedure Orders)

This is sort of intentional, i.e., the only Order type core knows about is TestOrder (and in 2.5, the ServiceOrder and I think there's a ReferralOrder). I recognise that not every implementation would solely use TestOrder for that, but since those order types are defined outside core, they're intended as a point an implementation can replace things with something that does support the order types they need to be mapped (it's a little weird, but the reason there are 50,000 interfaces in the module is to make it so they can be swapped with implementation-specific stuff).

private Set<Obs> results = new HashSet<>();

@OneToOne
@JoinColumn(name = "order_id", referencedColumnName = "order_id")
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 have a convention for the since annotation when dealing with new fields taken care of by lombok? https://openmrs.atlassian.net/wiki/spaces/docs/pages/25477044/Java+Conventions#New-Public-Methods-and-Classes

Copy link
Member Author

Choose a reason for hiding this comment

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

done. marked since 2.8.1, assuming you are ok to make a point release

package org.openmrs.module.fhir2.api.dao.impl;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

done

assertThat(reference, notNullValue());
}

@Ignore("this test case is no longer valid, as all other order subclasses now return a reference to ServiceRequest")
Copy link
Member

Choose a reason for hiding this comment

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

Should we then just delete it?

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 would say so

assertThat(result.getType(), equalTo(FhirConstants.MEDICATION_REQUEST));
}

@Ignore("this test case is not valid as the underlying implementation does not check against order.orderType")
Copy link
Member

Choose a reason for hiding this comment

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

Was this invalidated by the changes you introduced in this pull request? Or was it invalid even before?

Copy link
Member Author

Choose a reason for hiding this comment

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

For the title of the test, I can only infer the intention and see that the implementation was not asserting against the order.orderType, but rather based on the class of the order - whether TestOrder or DrugOrder; otherwise returning null. As I changed the implementation to always return a ServiceRequest reference for an order, this has started failing.

I do not know whether this was done to guard against a specific case. But just going by the test name, imo, the test is not right.

assertThat(result, nullValue());
}

@Ignore("this test case is not valid as the underlying implementation does not check against order.orderType")
Copy link
Member

Choose a reason for hiding this comment

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

Same as above.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, same as above. for "toFhirType_shouldReturnNullForUnknownOrderType()", ObservationBasedOnReferenceTranslatorImpl was never checking the orderType. If I have to do the right one, I have to inject "OrderService" in ObservationBasedOnReferenceTranslatorImpl, and check whether the orderType is a valid one. And in this case, I am not sure whether that should be the responsibility of this translator at all. This translator's job is to return a service request reference for observation - and imo, it should not go about checking the order type for the related order.

@angshu
Copy link
Member Author

angshu commented Dec 9, 2025

Not all implementations models ServiceRequest over TestOrder, and there are many Order/ServiceRequest types that should not be forced over TestOrder (e.g. Physiological Assessments, Psychological assessment, Surgical Procedure Orders)

This is sort of intentional, i.e., the only Order type core knows about is TestOrder (and in 2.5, the ServiceOrder and I think there's a ReferralOrder). I recognise that not every implementation would solely use TestOrder for that, but since those order types are defined outside core, they're intended as a point an implementation can replace things with something that does support the order types they need to be mapped (it's a little weird, but the reason there are 50,000 interfaces in the module is to make it so they can be swapped with implementation-specific stuff).

Do you want to keep it so? Imho, Openmrs core from the begining, very much allowed modelling other order types (e.g. surgical procedure, counselling requests can be modelled as Fhir ServiceRequest) through its base order model and most of the time thats enough. An order also can have order attribute. An implementation can always extend the order model through the additional attributes - and write their own translators as they deem fit, as you mentioned.

ServiceRequestTranslatorImpl - actually does not need TestOrder - it does not use any field like laterality, specimen_source, location, frequency or number_of_repeats! So it could have just used "Order" (and just differentiation of DrugOrder).

Anyway, in this card context - we are only talking about reference to the order - which imo, does not need anything other than an Order reference.

@angshu
Copy link
Member Author

angshu commented Dec 13, 2025

@ibacher @dkayiwa We want to change the relationship of Diagnostic report to service request to one to many, and therefore introduced a mapping table. Initially, we were thinking that a single reference to an order is enough, but in reality in many contexts multiple service requests maybe reported using a single report. e.g. Radiology tests (radiologist/physician asking for additional views, and multiple pathology test orders maybe reported using a single panel report. This model is supported in FHIR, and seems a real one. Please provide your thoughts and ideas, and whether you forsee any issues with it.

I can update this PR anytime. Also added more tests.

btw, let me know if you have additional comments on the Order reference resolution above, and the effected tests that I mentioned. If you are unsure, I propose that OrderReferenceTranslator.java does not reuse code from ReferenceHandlingTranslator. It would be a minor code duplication, but we can resolve that in future when we think of the larger order type mapping. If the contention is only about resolving reference to a service request, I think that would be prudent (at least for now)

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.

3 participants