Skip to content

VED-282-284-226 Delta refactoring extractor#416

Merged
robertnovac1 merged 29 commits intomasterfrom
VED-284-delta-elements-extraction
May 22, 2025
Merged

VED-282-284-226 Delta refactoring extractor#416
robertnovac1 merged 29 commits intomasterfrom
VED-284-delta-elements-extraction

Conversation

@robertnovac1
Copy link
Contributor

@robertnovac1 robertnovac1 commented May 16, 2025

Summary

  • Routine Change
  • ❗ Breaking Change
  • 🤖 Operational or Infrastructure Change
  • ✨ New Feature
  • ⚠️ Potential issues that might be caused by this change

Major overhaul of the delta JSON extraction logic.
Removed the following:

  • FHIRParser.py
  • SchemaParser.py
  • ConversionChecker.py

Added:

  • Refactored the converter to use tailored functionality to extract each field from the immunization event received by the delta.
  • Unit tests for all extraction logic
  • Updated readme.md
  • Simplified the conversion layout.

Reviews Required

  • Dev

Review Checklist

ℹ️ This section is to be filled in by the reviewer.

  • I have reviewed the changes in this PR and they fill all or part of the acceptance criteria of the ticket, and the code is in a mergeable state.
  • If there were infrastructure, operational, or build changes, I have made sure there is sufficient evidence that the changes will work.
  • I have ensured the changelog has been updated by the submitter, if necessary.

@robertnovac1 robertnovac1 changed the title delta refactoring extractor and converter VED-284 Delta refactoring extractor and converter May 19, 2025
# Conversion engine for expression-based field transformation
class ConversionChecker:
# checker settings
summarise = False
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe summarise and report_unexpected_exception can be removed now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kept only report_unexpected_exception, summarise has been removed as it wasn't used

FHIRConverter = Converter(json.dumps(resource_json))
flat_json = FHIRConverter.runConversion(resource_json) # Get the flat JSON
error_records = FHIRConverter.getErrorRecords()
FHIRConverter = Converter(json.dumps(resource_json), action_flag = action_flag)
Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming this is going to be cleaned up, but why are we parsing, then stringifying, then parsing again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that looks strange, I've fixed this


# get the value for a key
def getKeyValue(self, fieldName, flatFieldName, expression_type: str = "", expression_rule = ""):
def get_key_value(self, fieldName, flatFieldName, expression_type: str = "", expression_rule = ""):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can remove this entire file now 🙌

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fhir_parser has been removed

# Moved from file loading to JSON string better for elasticache


class SchemaParser:
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this needs to be a class. Could just do conversion_layout["conversions"] where we're currently calling get_conversions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

schema_parser has been removed

"expression": {
"expressionName": "Look Up",
"expressionType": "LOOKUP",
"expressionType": "NORMAL",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we just use the real conversion_layout.py in the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file has been removed


class Converter:

def __init__(self, fhir_data, action_flag = "UPDATE", report_unexpected_exception=True):
Copy link
Contributor

Choose a reason for hiding this comment

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

Can use ActionFlag.UPDATE

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@robertnovac1 robertnovac1 requested review from Akol125 and nhsdevws May 21, 2025 08:36
@robertnovac1 robertnovac1 changed the title VED-284 Delta refactoring extractor and converter VED-282-284-226 Delta refactoring extractor May 21, 2025
Copy link
Contributor

@Akol125 Akol125 left a comment

Choose a reason for hiding this comment

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

It's actually more straightforward, rather than use switch cases and expressionRules. Extractor is called in layout and it directly handles conversion logic, nice. The default postcode with zz, is that how you want to handle the default address if none, because it gives an impression that obfuscation was done on the address, which might be misleading, what do you think?

@robertnovac1 robertnovac1 requested review from Akol125 and mfjarvis May 21, 2025 12:49
Copy link
Contributor

@nhsdevws nhsdevws left a comment

Choose a reason for hiding this comment

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

Field management - use enums / constants is pretty much a should, magic strings can cause problems

@sonarqubecloud
Copy link

@robertnovac1 robertnovac1 requested a review from nhsdevws May 21, 2025 15:39
contained = self.fhir_json_data.get("contained", [])
return next((c for c in contained if isinstance(c, dict) and c.get("resourceType") == "Patient"), "")

def _get_valid_names(self, names, occurrence_time):
Copy link
Contributor

Choose a reason for hiding this comment

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

The spec has one extra rule - only consider names which have a given and family. I feel like we should question this though - follow up ticket?

return "", ""

practitioner_names = practitioner.get("name", [])
valid_practitioner_names = [n for n in practitioner_names if "given" in n or "family" in n]
Copy link
Contributor

Choose a reason for hiding this comment

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

The spec says we should have a given AND family. I feel like we should question this though - follow up ticket?

if not isinstance(performers, list) or not performers:
return "", ""

valid_performers = [p for p in performers if "actor" in p and "identifier" in p["actor"]]
Copy link
Contributor

Choose a reason for hiding this comment

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

The spec says we should prefer performers with a system unless there's only one specified - follow up ticket?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is being handled below


valid_addresses = [a for a in addresses if "postalCode" in a and self._is_current_period(a, occurrence_time)]
if not valid_addresses:
return self.DEFAULT_POSTCODE
Copy link
Contributor

Choose a reason for hiding this comment

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

If there's only one address, the spec says to use it even if it's not current at the time of vaccination - follow up ticket?


except Exception as e:
message = "DateTime conversion error [%s]: %s" % (e.__class__.__name__, e)
error = self._log_error("DATE_AND_TIME", message, e, code=exception_messages.UNEXPECTED_EXCEPTION)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could use field name constants here and a few other places in this file


def extract_unique_id(self):
identifier = self.fhir_json_data.get("identifier", [])
if identifier and len(identifier) == 1:
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure we need the length check?


if isinstance(primary_source, bool):
return primary_source
if str(primary_source).strip().lower() == "true":
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't think we should ever get something other than a boolean

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct - it's always boolean in backend api

Copy link
Contributor

@mfjarvis mfjarvis left a comment

Choose a reason for hiding this comment

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

Looks really good. Couple of minor differences from the spec, but happy for these to be looked at later

@robertnovac1 robertnovac1 merged commit c2696c9 into master May 22, 2025
8 checks passed
@robertnovac1 robertnovac1 deleted the VED-284-delta-elements-extraction branch May 22, 2025 14:50
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