Skip to content

[GPCAPIM-305]: Create common Pydantic FHIR types#109

Open
davidhamill1-nhs wants to merge 33 commits intomainfrom
feature/GPCAPIM-305_common_fhir_package
Open

[GPCAPIM-305]: Create common Pydantic FHIR types#109
davidhamill1-nhs wants to merge 33 commits intomainfrom
feature/GPCAPIM-305_common_fhir_package

Conversation

@davidhamill1-nhs
Copy link
Contributor

@davidhamill1-nhs davidhamill1-nhs commented Mar 11, 2026

Description

Pathology have created a lightweight FHIR package using Pydantic. Given we are using similar FHIR resources/elements, we will create a common package. This is the first step towards that: moving away from TypedDicts and copy-pastaing Pathology's package, before moving it in to the common repo.

Alongside this change I have actioned a PR comment from @nhsd-jack-wainwright that fitted well with this change - namely, having a response class, GetStructuredRecordResponse that handles all the response logic.

Context

To reduce duplication of code across the products/teams, and to save reinventing the wheel multiple times.

Type of changes

  • Refactoring (non-breaking change)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would change existing functionality)
  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have followed the code style of the project
  • I have added tests to cover my changes
  • I have updated the documentation accordingly
  • This PR is a result of pair or mob programming
  • Exceptions/Exclusions to coding standards (e.g. #noqa or #NOSONAR) are included within this Pull Request.

Sensitive Information Declaration

To ensure the utmost confidentiality and protect your and others privacy, we kindly ask you to NOT including PII (Personal Identifiable Information) / PID (Personal Identifiable Data) or any other sensitive data in this PR (Pull Request) and the codebase changes. We will remove any PR that do contain any sensitive information. We really appreciate your cooperation in this matter.

  • I confirm that neither PII/PID nor sensitive data are included in this PR and the codebase changes.

@davidhamill1-nhs davidhamill1-nhs requested a review from a team as a code owner March 11, 2026 00:19
Copilot AI review requested due to automatic review settings March 16, 2026 15:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a shared, strongly-typed FHIR model layer (Pydantic-based) for Gateway API and refactors request/response handling for the $gpc.getstructuredrecord flow to reduce TypedDict usage and duplication.

Changes:

  • Replace legacy TypedDict FHIR structures with Pydantic models (FHIR R4 + STU3 Parameters) and add extensive unit tests for validation/serialization.
  • Refactor $gpc.getstructuredrecord request parsing to validate STU3 Parameters, and introduce a GetStructuredRecordResponse helper for building Flask responses.
  • Update stubs, tests, lint config, and OpenAPI constraints to align with canonical FHIR identifier system URIs.

Reviewed changes

Copilot reviewed 60 out of 67 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
scripts/config/vale/styles/config/vocabularies/words/accept.txt Allow “Pydantic” / “validators” vocabulary in Vale.
ruff.toml Expand per-file ignores for tests (allow SLF001).
gateway-api/tests/integration/test_get_structured_record.py Update tests to use dict payloads instead of old Parameters type.
gateway-api/tests/conftest.py Update shared test fixture types to dict[str, Any].
gateway-api/tests/acceptance/steps/happy_path.py Update BDD steps to use dict payloads.
gateway-api/stubs/stubs/data/patients/alice_jones_9999999999.json Add identifier.system for org identifiers in stub data.
gateway-api/stubs/stubs/data/patients/blank_asid_sds_result_9000000011.json Add identifier.system for org identifiers in stub data.
gateway-api/stubs/stubs/data/patients/blank_endpoint_sds_result_9000000013.json Add identifier.system for org identifiers in stub data.
gateway-api/stubs/stubs/data/patients/induce_provider_error_9000000012.json Add identifier.system for org identifiers in stub data.
gateway-api/stubs/stubs/data/patients/no_sds_result_9000000010.json Add identifier.system for org identifiers in stub data.
gateway-api/stubs/stubs/data/patients/none_consumer_sds_result_9000000014.json Add identifier.system for org identifiers in stub data.
gateway-api/src/gateway_api/test_controller.py Update controller tests to use new fhir.r4.Patient model and response .json().
gateway-api/src/gateway_api/test_app.py Update app tests to mock controller returning a requests-like response and use dict payloads.
gateway-api/src/gateway_api/sds/client.py Parse SDS responses into typed Bundle/Device/Endpoint models and adjust extraction logic.
gateway-api/src/gateway_api/provider/test_client.py Update provider client tests to use dict request payloads.
gateway-api/src/gateway_api/pds/test_client.py Update PDS client tests to expect fhir.r4.Patient and validate error handling via Pydantic.
gateway-api/src/gateway_api/pds/search_results.py Remove legacy PdsSearchResults dataclass.
gateway-api/src/gateway_api/pds/client.py Change PDS client to return typed Patient via model_validate().
gateway-api/src/gateway_api/pds/init.py Remove export of deleted PdsSearchResults.
gateway-api/src/gateway_api/get_structured_record/test_response.py Add unit tests for new GetStructuredRecordResponse.
gateway-api/src/gateway_api/get_structured_record/test_request.py Update request tests to use dict fixtures; remove old FlaskResponse-based response tests.
gateway-api/src/gateway_api/get_structured_record/response.py New response builder helper for $gpc.getstructuredrecord.
gateway-api/src/gateway_api/get_structured_record/request.py Validate inbound JSON against STU3 Parameters model; expose headers/request_body via typed model.
gateway-api/src/gateway_api/get_structured_record/init.py Export GetStructuredRecordResponse.
gateway-api/src/gateway_api/controller.py Return the provider requests.Response directly and use typed PDS Patient.
gateway-api/src/gateway_api/conftest.py Update fixtures/types and align identifier system URIs.
gateway-api/src/gateway_api/common/error.py Build OperationOutcome via typed R4 models and expose operation_outcome property.
gateway-api/src/gateway_api/app.py Use GetStructuredRecordResponse for response construction and centralize error handling.
gateway-api/src/fhir/stu3/resources/parameters.py Add STU3 Parameters resource model.
gateway-api/src/fhir/stu3/resources/init.py Package marker (no functional changes shown).
gateway-api/src/fhir/stu3/elements/test_elements.py Add tests for STU3 Parameters validation/immutability.
gateway-api/src/fhir/stu3/elements/init.py Package marker (no functional changes shown).
gateway-api/src/fhir/stu3/init.py Export STU3 Parameters.
gateway-api/src/fhir/resources/resource.py Introduce polymorphic base Resource with resourceType dispatch and alias serialization.
gateway-api/src/fhir/resources/test_resource.py Add unit tests for Resource dispatch/create/dump semantics.
gateway-api/src/fhir/resources/init.py Export shared Resource base.
gateway-api/src/fhir/r4/resources/test_resources.py Add comprehensive tests for new R4 resources (Bundle/Patient/Device/Endpoint/OperationOutcome).
gateway-api/src/fhir/r4/resources/patient.py Add typed R4 Patient model + helpers (nhs_number, gp_ods_code).
gateway-api/src/fhir/r4/resources/operation_outcome.py Add typed R4 OperationOutcome model.
gateway-api/src/fhir/r4/resources/endpoint.py Add typed R4 Endpoint model.
gateway-api/src/fhir/r4/resources/device.py Add typed R4 Device model.
gateway-api/src/fhir/r4/resources/bundle.py Add typed R4 Bundle model + find_resources and empty().
gateway-api/src/fhir/r4/resources/init.py Package marker (no functional changes shown).
gateway-api/src/fhir/r4/resources/py.typed Mark package as typed.
gateway-api/src/fhir/r4/elements/test_elements.py Add tests for R4 elements (Identifier/Reference/Issue/Meta).
gateway-api/src/fhir/r4/elements/reference.py Add R4 Reference base with reference type validation.
gateway-api/src/fhir/r4/elements/meta.py Add R4 Meta element.
gateway-api/src/fhir/r4/elements/issue.py Add R4 Issue, IssueCode, IssueSeverity.
gateway-api/src/fhir/r4/elements/identifier.py Add R4 Identifier types (UUID/NHS Number).
gateway-api/src/fhir/r4/elements/init.py Package marker (no functional changes shown).
gateway-api/src/fhir/r4/elements/py.typed Mark package as typed.
gateway-api/src/fhir/r4/init.py Export R4 elements/resources as a public entry point.
gateway-api/src/fhir/period.py Remove legacy TypedDict Period.
gateway-api/src/fhir/patient.py Remove legacy TypedDict Patient.
gateway-api/src/fhir/parameters.py Remove legacy TypedDict Parameters.
gateway-api/src/fhir/operation_outcome.py Remove legacy TypedDict OperationOutcome.
gateway-api/src/fhir/identifier.py Remove legacy TypedDict Identifier.
gateway-api/src/fhir/human_name.py Remove legacy TypedDict HumanName.
gateway-api/src/fhir/general_practitioner.py Remove legacy TypedDict GeneralPractitioner.
gateway-api/src/fhir/bundle.py Remove legacy TypedDict Bundle.
gateway-api/src/fhir/init.py Remove legacy fhir re-export module (now namespace-style structure).
gateway-api/src/fhir/README.md Add documentation for the new FHIR typing approach and examples.
gateway-api/pyproject.toml Add pydantic dependency.
gateway-api/openapi.yaml Constrain identifier.system via enum to the NHS number URI.
Makefile Adjust wheel install platform targets.
.vscode/cspell-dictionary.txt Add “searchset” to cspell dictionary.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copilot AI review requested due to automatic review settings March 16, 2026 16:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the existing FHIR TypedDict approach with a shared Pydantic-based FHIR model layer (R4 + STU3) and refactors request/response handling for /$gpc.getstructuredrecord to use these typed models across PDS/SDS/controller/app, updating tests and stubs accordingly.

Changes:

  • Introduces a new fhir Pydantic model package (shared Resource base + selected R4 resources/elements + STU3 Parameters) and removes old TypedDict FHIR types.
  • Refactors Gateway API flow to validate inbound STU3 Parameters, parse upstream PDS/SDS responses as typed R4 models, and centralises HTTP response building in GetStructuredRecordResponse.
  • Updates test suites, stub payloads, OpenAPI schema, and tooling configs to align with the new typing/validation.

Reviewed changes

Copilot reviewed 62 out of 69 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/config/vale/styles/config/vocabularies/words/accept.txt Allows “Pydantic” and “validators” in Vale vocabulary.
ruff.toml Expands per-file ignores for tests (incl. SLF001).
gateway-api/tests/integration/test_get_structured_record.py Updates test typing away from legacy Parameters type.
gateway-api/tests/contract/conftest.py Removes verify=False from proxying requests.
gateway-api/tests/conftest.py Updates simple_request_payload fixture typing to dict.
gateway-api/tests/acceptance/steps/happy_path.py Updates step typing away from legacy Parameters type.
gateway-api/stubs/stubs/data/patients/none_consumer_sds_result_9000000014.json Adds ODS identifier system to stub payload.
gateway-api/stubs/stubs/data/patients/no_sds_result_9000000010.json Adds ODS identifier system to stub payload.
gateway-api/stubs/stubs/data/patients/induce_provider_error_9000000012.json Adds ODS identifier system to stub payload.
gateway-api/stubs/stubs/data/patients/blank_endpoint_sds_result_9000000013.json Adds ODS identifier system to stub payload.
gateway-api/stubs/stubs/data/patients/blank_asid_sds_result_9000000011.json Adds ODS identifier system to stub payload.
gateway-api/stubs/stubs/data/patients/alice_jones_9999999999.json Adds ODS identifier system to stub payload.
gateway-api/src/gateway_api/test_controller.py Updates controller unit tests to use typed Patient and response .json().
gateway-api/src/gateway_api/test_app.py Updates app tests to use dict payloads and mocked response .json().
gateway-api/src/gateway_api/sds/client.py Changes SDS client to validate responses as typed R4 Bundle/Device/Endpoint.
gateway-api/src/gateway_api/provider/test_client.py Updates provider client tests to use dict request payloads.
gateway-api/src/gateway_api/pds/test_client.py Updates PDS tests to assert typed Patient properties.
gateway-api/src/gateway_api/pds/search_results.py Removes PdsSearchResults dataclass (no longer used).
gateway-api/src/gateway_api/pds/client.py Returns typed Patient via Pydantic validation (replacing extraction logic).
gateway-api/src/gateway_api/pds/init.py Removes PdsSearchResults export.
gateway-api/src/gateway_api/get_structured_record/test_response.py Adds tests for new GetStructuredRecordResponse.
gateway-api/src/gateway_api/get_structured_record/test_request.py Updates request tests for STU3 typed Parameters validation path.
gateway-api/src/gateway_api/get_structured_record/response.py Adds GetStructuredRecordResponse response builder.
gateway-api/src/gateway_api/get_structured_record/request.py Validates inbound body as STU3 Parameters and exposes typed accessors.
gateway-api/src/gateway_api/get_structured_record/init.py Exports GetStructuredRecordResponse.
gateway-api/src/gateway_api/controller.py Returns provider requests.Response directly and consumes typed Patient from PDS.
gateway-api/src/gateway_api/conftest.py Updates fixtures and request factory to use dict payloads and NHS number system URI.
gateway-api/src/gateway_api/common/error.py Refactors errors to build typed R4 OperationOutcome via Pydantic models.
gateway-api/src/gateway_api/app.py Refactors route to use GetStructuredRecordResponse and typed error outcomes.
gateway-api/src/fhir/stu3/resources/parameters.py Adds STU3 Parameters resource model for inbound validation.
gateway-api/src/fhir/stu3/resources/init.py Adds STU3 resources package marker.
gateway-api/src/fhir/stu3/elements/test_elements.py Adds tests for STU3 Parameters validation/serialization.
gateway-api/src/fhir/stu3/elements/init.py Adds STU3 elements package marker.
gateway-api/src/fhir/stu3/init.py Exposes STU3 Parameters.
gateway-api/src/fhir/resources/test_resource.py Adds tests for polymorphic Resource dispatch and dump behaviour.
gateway-api/src/fhir/resources/resource.py Adds shared polymorphic Resource base with subtype dispatch + resourceType validation.
gateway-api/src/fhir/resources/init.py Exports shared Resource.
gateway-api/src/fhir/r4/resources/test_resources.py Adds tests for R4 resources (Bundle, Patient, Device, Endpoint, OperationOutcome).
gateway-api/src/fhir/r4/resources/py.typed Marks R4 resources package as typed.
gateway-api/src/fhir/r4/resources/patient.py Adds typed R4 Patient resource with nhs_number/gp_ods_code helpers.
gateway-api/src/fhir/r4/resources/operation_outcome.py Adds typed R4 OperationOutcome resource.
gateway-api/src/fhir/r4/resources/endpoint.py Adds typed R4 Endpoint resource.
gateway-api/src/fhir/r4/resources/device.py Adds typed R4 Device resource with constrained identifier types.
gateway-api/src/fhir/r4/resources/bundle.py Adds typed R4 Bundle resource with find_resources.
gateway-api/src/fhir/r4/resources/init.py Adds R4 resources package marker.
gateway-api/src/fhir/r4/elements/test_elements.py Adds tests for R4 elements (Identifier, Reference, Meta, Issue).
gateway-api/src/fhir/r4/elements/reference.py Adds typed R4 Reference base with reference-type validation.
gateway-api/src/fhir/r4/elements/py.typed Marks R4 elements package as typed.
gateway-api/src/fhir/r4/elements/meta.py Adds typed R4 Meta element.
gateway-api/src/fhir/r4/elements/issue.py Adds typed R4 Issue/IssueCode/IssueSeverity.
gateway-api/src/fhir/r4/elements/identifier.py Adds typed R4 Identifier + constrained subclasses (UUID/NHS number).
gateway-api/src/fhir/r4/elements/init.py Adds R4 elements package marker.
gateway-api/src/fhir/r4/init.py Exposes R4 elements/resources via fhir.r4.
gateway-api/src/fhir/period.py Removes legacy TypedDict Period.
gateway-api/src/fhir/patient.py Removes legacy TypedDict Patient.
gateway-api/src/fhir/parameters.py Removes legacy TypedDict Parameters.
gateway-api/src/fhir/operation_outcome.py Removes legacy TypedDict OperationOutcome.
gateway-api/src/fhir/identifier.py Removes legacy TypedDict Identifier.
gateway-api/src/fhir/human_name.py Removes legacy TypedDict HumanName.
gateway-api/src/fhir/general_practitioner.py Removes legacy TypedDict GeneralPractitioner.
gateway-api/src/fhir/bundle.py Removes legacy TypedDict Bundle.
gateway-api/src/fhir/init.py Clears legacy fhir re-exports (now empty).
gateway-api/src/fhir/README.md Adds documentation for new typed FHIR approach and usage.
gateway-api/pyproject.toml Adds pydantic dependency and includes fhir package.
gateway-api/openapi.yaml Constrains valueIdentifier.system to the NHS number URI via enum.
Makefile Adds additional musllinux platform target for wheel install.
.vscode/cspell-dictionary.txt Adds “searchset” to spelling dictionary.
.github/workflows/preview-env.yml Bumps Trivy action SHAs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

Trivy gate: no Critical/High issues.

Trivy IaC (Terraform) Summary

Severity Count
CRITICAL 0
HIGH 0
MEDIUM 0
LOW 0
UNKNOWN 0
Findings (top 50)
Severity ID Title File

@github-actions
Copy link

github-actions bot commented Mar 16, 2026

Trivy gate: no Critical/High vulnerabilities.

Trivy Image Scan Summary

Image: 900119715266.dkr.ecr.eu-west-2.amazonaws.com/whoami:feature-gpcapim-305-common-fhir-package

Severity Count
CRITICAL 0
HIGH 0
MEDIUM 0
LOW 1
UNKNOWN 0
Findings (top 50)
Severity ID Package Installed Fixed Source
LOW CVE-2026-1703 pip 25.3 26.0 Python

Copilot AI review requested due to automatic review settings March 17, 2026 15:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a shared, Pydantic-based set of FHIR types (STU3 + R4) and refactors Gateway API request/response handling and upstream client parsing to use these typed models instead of TypedDicts.

Changes:

  • Added Pydantic FHIR models (R4 + STU3) including a polymorphic Resource base for validation/serialization.
  • Refactored PDS/SDS client parsing and controller/test code to use typed FHIR models.
  • Introduced GetStructuredRecordResponse to centralize response construction and (currently) mirror request headers.

Reviewed changes

Copilot reviewed 63 out of 70 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
scripts/config/vale/styles/config/vocabularies/words/accept.txt Allow-list new terms for Vale linting.
ruff.toml Adjust test per-file ignores (incl. private member access in tests).
infrastructure/images/gateway-api/Dockerfile apk upgrade during image build before creating non-root user.
gateway-api/tests/integration/test_get_structured_record.py Update request payload typing and add header-mirroring integration test.
gateway-api/tests/contract/conftest.py Remove verify=False from proxy request forwarding.
gateway-api/tests/conftest.py Update request payload fixture typing away from old FHIR TypedDict.
gateway-api/tests/acceptance/steps/happy_path.py Update payload typing/imports for acceptance steps.
gateway-api/stubs/stubs/data/patients/alice_jones_9999999999.json Add ODS identifier system to stub patient payload.
gateway-api/stubs/stubs/data/patients/no_sds_result_9000000010.json Add ODS identifier system to stub patient payload.
gateway-api/stubs/stubs/data/patients/blank_asid_sds_result_9000000011.json Add ODS identifier system to stub patient payload.
gateway-api/stubs/stubs/data/patients/induce_provider_error_9000000012.json Add ODS identifier system to stub patient payload.
gateway-api/stubs/stubs/data/patients/blank_endpoint_sds_result_9000000013.json Add ODS identifier system to stub patient payload.
gateway-api/stubs/stubs/data/patients/none_consumer_sds_result_9000000014.json Add ODS identifier system to stub patient payload.
gateway-api/src/gateway_api/test_controller.py Update unit tests to use typed FHIR Patient and requests-style responses.
gateway-api/src/gateway_api/test_app.py Update app tests to mock controller responses and simplify typing.
gateway-api/src/gateway_api/sds/client.py Parse SDS responses into typed Bundle/Device/Endpoint models and simplify extraction.
gateway-api/src/gateway_api/provider/test_client.py Update provider client tests to use dict payload typing.
gateway-api/src/gateway_api/pds/test_client.py Update PDS tests for typed Patient parsing and add validation failure test.
gateway-api/src/gateway_api/pds/search_results.py Remove old PdsSearchResults dataclass.
gateway-api/src/gateway_api/pds/client.py Refactor PDS client to return typed Patient via Pydantic validation.
gateway-api/src/gateway_api/pds/init.py Update exports after removing PdsSearchResults.
gateway-api/src/gateway_api/get_structured_record/test_response.py Add unit tests for new response builder.
gateway-api/src/gateway_api/get_structured_record/test_request.py Update request tests for new typed request parsing and remove old response-building tests.
gateway-api/src/gateway_api/get_structured_record/response.py Introduce GetStructuredRecordResponse response builder.
gateway-api/src/gateway_api/get_structured_record/request.py Parse/validate STU3 Parameters via Pydantic and expose typed accessors.
gateway-api/src/gateway_api/get_structured_record/init.py Export GetStructuredRecordResponse.
gateway-api/src/gateway_api/controller.py Return provider HTTP response directly; refactor PDS handling to use typed Patient.
gateway-api/src/gateway_api/conftest.py Update fixtures to dict payloads and align identifier system URIs.
gateway-api/src/gateway_api/common/error.py Refactor error handling to build typed R4 OperationOutcome.
gateway-api/src/gateway_api/app.py Use GetStructuredRecordResponse for consistent response building and error handling.
gateway-api/src/fhir/stu3/resources/parameters.py Add STU3 Parameters resource model for inbound request validation.
gateway-api/src/fhir/stu3/elements/test_elements.py Add tests for STU3 Parameters validation/immutability.
gateway-api/src/fhir/stu3/init.py Add STU3 package exports.
gateway-api/src/fhir/resources/resource.py Add polymorphic Resource base with resourceType dispatch + exclude-none serialization.
gateway-api/src/fhir/resources/test_resource.py Add tests for polymorphic Resource dispatch and serialization.
gateway-api/src/fhir/resources/init.py Export shared Resource base.
gateway-api/src/fhir/r4/init.py Export R4 elements/resources for typed usage.
gateway-api/src/fhir/r4/elements/identifier.py Add Identifier/UUID/NHS-number identifier elements.
gateway-api/src/fhir/r4/elements/issue.py Add OperationOutcome Issue element + enums.
gateway-api/src/fhir/r4/elements/meta.py Add Meta element with aliasing helpers.
gateway-api/src/fhir/r4/elements/reference.py Add typed Reference base with reference-type validation.
gateway-api/src/fhir/r4/elements/test_elements.py Add tests for R4 elements validation/immutability.
gateway-api/src/fhir/r4/resources/bundle.py Add typed Bundle resource + find_resources.
gateway-api/src/fhir/r4/resources/device.py Add typed Device resource for SDS parsing.
gateway-api/src/fhir/r4/resources/endpoint.py Add typed Endpoint resource for SDS parsing.
gateway-api/src/fhir/r4/resources/operation_outcome.py Add typed OperationOutcome resource.
gateway-api/src/fhir/r4/resources/patient.py Add typed Patient resource with convenient NHS number / GP ODS accessors.
gateway-api/src/fhir/r4/resources/test_resources.py Add tests for R4 resources creation/validation and helpers.
gateway-api/src/fhir/README.md New documentation describing typed FHIR approach and usage.
gateway-api/src/fhir/bundle.py Remove old TypedDict bundle.
gateway-api/src/fhir/general_practitioner.py Remove old TypedDict generalPractitioner.
gateway-api/src/fhir/human_name.py Remove old TypedDict humanName.
gateway-api/src/fhir/identifier.py Remove old TypedDict identifier.
gateway-api/src/fhir/operation_outcome.py Remove old TypedDict operation outcome.
gateway-api/src/fhir/parameters.py Remove old TypedDict parameters.
gateway-api/src/fhir/patient.py Remove old TypedDict patient.
gateway-api/src/fhir/period.py Remove old TypedDict period.
gateway-api/src/fhir/init.py Remove old umbrella exports for TypedDict-based FHIR types.
gateway-api/pyproject.toml Add pydantic dependency.
gateway-api/openapi.yaml Constrain identifier system to NHS number URI via enum.
Makefile Add additional musllinux platform tag to packaging install step.
.vscode/cspell-dictionary.txt Add “searchset” to dictionary.
.github/workflows/preview-env.yml Update Trivy action pins.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copilot AI review requested due to automatic review settings March 17, 2026 21:53
@github-actions
Copy link

Deployment Complete

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR begins the migration from ad-hoc TypedDict FHIR structures to a shared, typed Pydantic-based FHIR model layer (STU3 + R4) within gateway-api, and refactors the /patient/$gpc.getstructuredrecord flow to use a dedicated response builder that mirrors selected headers.

Changes:

  • Introduces a new Pydantic-based fhir package structure (R4 + STU3) with a polymorphic Resource base and typed resources/elements.
  • Refactors PDS and SDS clients (and related tests/stubs) to parse/validate upstream FHIR JSON into typed models.
  • Adds GetStructuredRecordResponse to centralize response construction (including header mirroring) and updates app/controller/tests accordingly.

Reviewed changes

Copilot reviewed 63 out of 70 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/config/vale/styles/config/vocabularies/words/accept.txt Allows “Pydantic”/“validators” in Vale vocabulary.
ruff.toml Expands per-file ignores for tests (incl. SLF001).
infrastructure/images/gateway-api/Dockerfile Adds apk upgrade during image build.
gateway-api/tests/integration/test_get_structured_record.py Updates request payload typing; adds header-mirroring integration test.
gateway-api/tests/contract/conftest.py Removes verify=False from proxying requests.
gateway-api/tests/conftest.py Updates request fixture typing to plain dict.
gateway-api/tests/acceptance/steps/happy_path.py Updates request payload typing in BDD steps.
gateway-api/stubs/stubs/data/patients/alice_jones_9999999999.json Adds ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/no_sds_result_9000000010.json Adds ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/blank_asid_sds_result_9000000011.json Adds ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/induce_provider_error_9000000012.json Adds ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/blank_endpoint_sds_result_9000000013.json Adds ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/none_consumer_sds_result_9000000014.json Adds ODS identifier system to stub data.
gateway-api/src/gateway_api/test_controller.py Updates controller unit tests to use typed Patient model.
gateway-api/src/gateway_api/test_app.py Updates app tests to expect .json()-style controller responses.
gateway-api/src/gateway_api/sds/client.py Switches SDS parsing to typed Bundle/Device/Endpoint models.
gateway-api/src/gateway_api/provider/test_client.py Updates provider client tests’ request payload typing.
gateway-api/src/gateway_api/pds/test_client.py Refactors PDS tests to assert typed Patient behavior/errors.
gateway-api/src/gateway_api/pds/search_results.py Removes legacy PdsSearchResults dataclass.
gateway-api/src/gateway_api/pds/client.py Refactors PDS client to return typed Patient (Pydantic validation).
gateway-api/src/gateway_api/pds/init.py Updates exports to remove PdsSearchResults.
gateway-api/src/gateway_api/get_structured_record/test_response.py Adds tests for new GetStructuredRecordResponse.
gateway-api/src/gateway_api/get_structured_record/test_request.py Updates request tests to dict payload typing; removes old response-building tests.
gateway-api/src/gateway_api/get_structured_record/response.py Introduces response builder with header mirroring + provider/error handling.
gateway-api/src/gateway_api/get_structured_record/request.py Validates inbound STU3 Parameters with Pydantic; exposes typed fields/JSON.
gateway-api/src/gateway_api/get_structured_record/init.py Exposes GetStructuredRecordResponse.
gateway-api/src/gateway_api/controller.py Returns provider requests.Response directly; uses typed Patient from PDS.
gateway-api/src/gateway_api/conftest.py Updates shared test fixtures for request/response JSON systems/typing.
gateway-api/src/gateway_api/common/error.py Switches errors to typed R4 OperationOutcome construction.
gateway-api/src/gateway_api/app.py Uses GetStructuredRecordResponse for consistent output + header mirroring.
gateway-api/src/fhir/stu3/resources/parameters.py Adds STU3 Parameters model for request validation.
gateway-api/src/fhir/stu3/elements/test_elements.py Adds STU3 Parameters validation/unit tests.
gateway-api/src/fhir/stu3/init.py Exposes STU3 Parameters.
gateway-api/src/fhir/resources/test_resource.py Adds tests for polymorphic Resource dispatch + serialization behavior.
gateway-api/src/fhir/resources/resource.py Adds polymorphic Resource base and create()/serialization defaults.
gateway-api/src/fhir/resources/init.py Exposes shared Resource.
gateway-api/src/fhir/r4/resources/test_resources.py Adds comprehensive R4 resource tests (Bundle/Patient/Device/Endpoint/Outcome).
gateway-api/src/fhir/r4/resources/patient.py Adds typed R4 Patient model + helpers/properties.
gateway-api/src/fhir/r4/resources/operation_outcome.py Adds typed R4 OperationOutcome model.
gateway-api/src/fhir/r4/resources/endpoint.py Adds typed R4 Endpoint model.
gateway-api/src/fhir/r4/resources/device.py Adds typed R4 Device model.
gateway-api/src/fhir/r4/resources/bundle.py Adds typed R4 Bundle model + resource-finding helpers.
gateway-api/src/fhir/r4/elements/test_elements.py Adds comprehensive R4 element tests (Identifier/Reference/Meta/Issue).
gateway-api/src/fhir/r4/elements/reference.py Adds typed R4 Reference base model.
gateway-api/src/fhir/r4/elements/meta.py Adds typed R4 Meta element.
gateway-api/src/fhir/r4/elements/issue.py Adds typed R4 Issue element + enums.
gateway-api/src/fhir/r4/elements/identifier.py Adds typed R4 Identifier element + concrete identifier types.
gateway-api/src/fhir/r4/init.py Exposes R4 resources/elements as a public module surface.
gateway-api/src/fhir/period.py Removes legacy TypedDict Period.
gateway-api/src/fhir/patient.py Removes legacy TypedDict Patient.
gateway-api/src/fhir/parameters.py Removes legacy TypedDict Parameters.
gateway-api/src/fhir/operation_outcome.py Removes legacy TypedDict OperationOutcome.
gateway-api/src/fhir/identifier.py Removes legacy TypedDict Identifier.
gateway-api/src/fhir/human_name.py Removes legacy TypedDict HumanName.
gateway-api/src/fhir/general_practitioner.py Removes legacy TypedDict GeneralPractitioner.
gateway-api/src/fhir/bundle.py Removes legacy TypedDict Bundle.
gateway-api/src/fhir/init.py Removes legacy fhir module re-exports.
gateway-api/src/fhir/README.md Adds module-level documentation for the new typed FHIR approach.
gateway-api/pyproject.toml Adds pydantic dependency.
gateway-api/openapi.yaml Constrains NHS number identifier system via enum.
Makefile Adds additional musllinux platform in packaging step.
.vscode/cspell-dictionary.txt Adds “searchset” spelling entry.
.github/workflows/preview-env.yml Updates pinned Trivy action SHAs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 54 to +56
@property
def request_body(self) -> str:
return json.dumps(self._request_body)
return self.parameters.model_dump_json()
@davidhamill1-nhs davidhamill1-nhs force-pushed the feature/GPCAPIM-305_common_fhir_package branch from 7e1f496 to 6270716 Compare March 18, 2026 12:32
Copilot AI review requested due to automatic review settings March 19, 2026 09:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new internal FHIR modelling layer based on Pydantic (replacing prior TypedDict-based FHIR types) and refactors request/response handling for the /$gpc.getstructuredrecord operation to centralise response construction and header mirroring.

Changes:

  • Add Pydantic-based FHIR Resource/element models (R4 + STU3 Parameters) and supporting tests/docs.
  • Refactor Gateway API request/response flow: validate inbound STU3 Parameters, return provider responses via a dedicated GetStructuredRecordResponse.
  • Update PDS/SDS clients, stubs, and test suites to use the new typed FHIR models and URI systems.

Reviewed changes

Copilot reviewed 60 out of 67 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/config/vale/styles/config/vocabularies/words/accept.txt Allow “Pydantic” and “validators” in Vale vocab.
ruff.toml Extend test file ignores to allow private member access in tests.
gateway-api/tests/integration/test_get_structured_record.py Update tests to use dict payloads and add header-mirroring assertion.
gateway-api/tests/conftest.py Update fixtures to return dict payloads rather than old FHIR TypedDict types.
gateway-api/tests/acceptance/steps/happy_path.py Update step typing to dict payloads (drop old Parameters import).
gateway-api/stubs/stubs/data/patients/none_consumer_sds_result_9000000014.json Add ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/no_sds_result_9000000010.json Add ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/induce_provider_error_9000000012.json Add ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/blank_endpoint_sds_result_9000000013.json Add ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/blank_asid_sds_result_9000000011.json Add ODS identifier system to stub data.
gateway-api/stubs/stubs/data/patients/alice_jones_9999999999.json Add ODS identifier system to stub data.
gateway-api/src/gateway_api/test_controller.py Update controller tests to use new typed Patient and response json handling.
gateway-api/src/gateway_api/test_app.py Update app tests to mock controller responses and expect JSON payloads as dicts.
gateway-api/src/gateway_api/sds/client.py Switch SDS parsing to typed Bundle/Device/Endpoint models.
gateway-api/src/gateway_api/provider/test_client.py Update provider client tests to use dict payloads.
gateway-api/src/gateway_api/pds/test_client.py Update PDS client tests to assert typed Patient behaviour; simplify negative test coverage.
gateway-api/src/gateway_api/pds/search_results.py Remove legacy PdsSearchResults dataclass.
gateway-api/src/gateway_api/pds/client.py Refactor PDS client to return typed Patient via Pydantic validation.
gateway-api/src/gateway_api/pds/init.py Stop exporting removed PdsSearchResults.
gateway-api/src/gateway_api/get_structured_record/test_response.py Add unit tests for new GetStructuredRecordResponse.
gateway-api/src/gateway_api/get_structured_record/test_request.py Update request tests; remove tests for deleted FlaskResponse-based response-building.
gateway-api/src/gateway_api/get_structured_record/response.py Introduce response builder to mirror headers and construct FHIR+json responses.
gateway-api/src/gateway_api/get_structured_record/request.py Validate inbound payload as STU3 Parameters model and expose typed accessors.
gateway-api/src/gateway_api/get_structured_record/init.py Export new GetStructuredRecordResponse.
gateway-api/src/gateway_api/controller.py Update controller to return raw requests.Response from provider instead of custom FlaskResponse.
gateway-api/src/gateway_api/conftest.py Update shared fixtures and request helpers to dict payloads and updated identifier system URIs.
gateway-api/src/gateway_api/common/error.py Rework error modelling to build typed R4 OperationOutcome instances.
gateway-api/src/gateway_api/clinical_jwt/jwt.py Remove mypy ignore around pyjwt.encode call.
gateway-api/src/gateway_api/app.py Use GetStructuredRecordResponse to build final Flask response and mirror headers.
gateway-api/src/fhir/stu3/elements/test_elements.py Add tests for STU3 Parameters model validation/immutability.
gateway-api/src/fhir/stu3/elements/parameters.py Add STU3 Parameters resource model.
gateway-api/src/fhir/stu3/elements/init.py Package init placeholder for STU3 elements.
gateway-api/src/fhir/stu3/init.py Export STU3 Parameters.
gateway-api/src/fhir/resources/test_resource.py Add tests for Resource dispatch, Meta, and dump behaviour.
gateway-api/src/fhir/resources/resource.py Introduce base Resource and Meta implementation with subtype dispatch.
gateway-api/src/fhir/resources/init.py Package init placeholder for shared resource infrastructure.
gateway-api/src/fhir/r4/resources/test_resources.py Add tests for new R4 resources and helper behaviours.
gateway-api/src/fhir/r4/resources/py.typed Mark fhir.r4.resources as typed for type checkers.
gateway-api/src/fhir/r4/resources/patient.py Add typed R4 Patient model with NHS number / GP ODS helpers.
gateway-api/src/fhir/r4/resources/operation_outcome.py Add typed R4 OperationOutcome model.
gateway-api/src/fhir/r4/resources/endpoint.py Add typed R4 Endpoint model.
gateway-api/src/fhir/r4/resources/device.py Add typed R4 Device model.
gateway-api/src/fhir/r4/resources/bundle.py Add typed R4 Bundle model with find_resources helper.
gateway-api/src/fhir/r4/resources/init.py Package init placeholder for R4 resources.
gateway-api/src/fhir/r4/elements/test_elements.py Add tests for new R4 element types and validation expectations.
gateway-api/src/fhir/r4/elements/reference.py Add typed R4 Reference base model with reference-type validation.
gateway-api/src/fhir/r4/elements/py.typed Mark fhir.r4.elements as typed for type checkers.
gateway-api/src/fhir/r4/elements/issue.py Add issue severity/code enums and Issue element dataclass.
gateway-api/src/fhir/r4/elements/identifier.py Add Identifier/UUID/NHS-number identifier element types.
gateway-api/src/fhir/r4/elements/init.py Package init placeholder for R4 elements.
gateway-api/src/fhir/r4/init.py Export R4 elements/resources for simplified imports.
gateway-api/src/fhir/period.py Remove legacy TypedDict Period type.
gateway-api/src/fhir/patient.py Remove legacy TypedDict Patient type.
gateway-api/src/fhir/parameters.py Remove legacy TypedDict Parameters type.
gateway-api/src/fhir/operation_outcome.py Remove legacy TypedDict OperationOutcome type.
gateway-api/src/fhir/identifier.py Remove legacy TypedDict Identifier type.
gateway-api/src/fhir/human_name.py Remove legacy TypedDict HumanName type.
gateway-api/src/fhir/general_practitioner.py Remove legacy TypedDict GeneralPractitioner type.
gateway-api/src/fhir/bundle.py Remove legacy TypedDict Bundle type.
gateway-api/src/fhir/init.py Repoint fhir package to export the new Resource base.
gateway-api/src/fhir/README.md Document the new typed FHIR approach and version split (STU3 inbound vs R4 internal).
gateway-api/openapi.yaml Constrain request identifier.system to the NHS-number URI via enum.
gateway-api/pyproject.toml Add Pydantic dependency; remove pytest ini options section and a dev dependency.
Makefile Adjust wheel install platforms for build target.
.vscode/cspell-dictionary.txt Add “searchset” to workspace spell dictionary.
.github/workflows/preview-env.yml Attempt to add SBOM generation step to preview workflow.
Comments suppressed due to low confidence (2)

.github/workflows/preview-env.yml:443

  • The “Generate SBOM” step has two uses: keys (sbom-scan and image-scan). GitHub Actions steps can only have a single uses (or run), so this will make the workflow YAML invalid. Split this into two separate steps (one for sbom-scan, one for image-scan) or remove the unintended uses line.
      - name: Generate SBOM
        uses: nhs-england-tools/trivy-action/sbom-scan@289984b2f03034233a347d6dbadecd5ca9ea9634
        if: github.event.action != 'closed'
        uses: nhs-england-tools/trivy-action/image-scan@289984b2f03034233a347d6dbadecd5ca9ea9634
        with:
          image-ref: ${{steps.meta.outputs.ecr_url}}:${{steps.meta.outputs.branch_name}}
          artifact-name: trivy-sbom-${{ steps.meta.outputs.branch_name }}

gateway-api/pyproject.toml:65

  • [tool.pytest.ini_options] has been removed, but acceptance tests use pytest-bdd scenarios referencing feature files by name only (e.g. @scenario("happy_path.feature", ...)) while the feature lives under tests/acceptance/features/. Without bdd_features_base_dir, pytest-bdd won’t resolve these feature files and the acceptance suite will fail. Reintroduce the pytest ini options (at least bdd_features_base_dir = "tests/acceptance/features", and ideally the marker registrations that were removed).
[tool.mypy]
strict = true


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +14 to +26
class PatientIdentifier(
Identifier, expected_system="https://fhir.nhs.uk/Id/nhs-number"
):
"""A FHIR R4 Patient Identifier utilising the NHS Number system."""

def __init__(self, value: str):
super().__init__(value=value, system=self._expected_system)

@classmethod
def from_nhs_number(cls, nhs_number: str) -> "Patient.PatientIdentifier":
"""Create a PatientIdentifier from an NHS number."""
return cls(value=nhs_number)

Comment on lines +79 to +80
# provided a generic dictonary object, delegate to the normal handler.
if cls != Resource or not isinstance(value, dict):
Comment on lines 36 to 42
def encode(self) -> str:
return pyjwt.encode(
self.payload(),
key=None, # type: ignore[arg-type]
key=None,
algorithm=self.algorithm,
headers={"typ": self.type},
)
Comment on lines +9 to +35
@dataclass(frozen=True)
class Identifier(ABC):
"""
A FHIR R4 Identifier element. See https://hl7.org/fhir/R4/datatypes.html#Identifier.
Attributes:
system: The namespace for the identifier value.
value: The value that is unique within the system.
"""

_expected_system: ClassVar[str] = "__unknown__"

value: str
system: str

@model_validator(mode="after")
def validate_system(self) -> "Identifier":
if self.system != self._expected_system:
raise ValueError(
f"Identifier system '{self.system}' does not match expected "
f"system '{self._expected_system}'."
)
return self

@classmethod
def __init_subclass__(cls, expected_system: str) -> None:
cls._expected_system = expected_system

Comment on lines +40 to +43
def __init__(self, value: uuid.UUID | None = None):
super().__init__(
value=str(value or uuid.uuid4()),
system=self._expected_system,
Copy link
Contributor Author

@davidhamill1-nhs davidhamill1-nhs Mar 19, 2026

Choose a reason for hiding this comment

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

Using the Patient model enabled logic to be removed from this file:

  • _get_gp_ods_code implemented finding the first GP practice. For a PDS FHIR response there will only be at most one GP practice, the current one. As such, Patient.gp_ods_code will return the current GP practice or None, mimicking this behaviour with less code..
  • Using Patient removes the need to use PDSSearchResults.
  • Given we will only ever be retrieving a single PDS record with GET /Patient/<nhs_number>, I have also removed the PdsClient._extract_single_search_result() method and have PdsClient.search_patient_by_nhs_number() simply return the Patient model-validated body.

@sonarqubecloud
Copy link

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.

2 participants