Convert-Pheno

 view release on metacpan or  search on metacpan

t/21-openehr-behavior.t  view on Meta::CPAN

#!/usr/bin/env perl
use strict;
use warnings;

use lib qw(./lib ../lib t/lib);
use Test::More;
use Test::ConvertPheno qw(
  build_convert
  load_json_file
  temp_output_file
  write_json_file
  structured_files_match
);

my $gender = load_json_file('t/openehr2bff/in/gecco_personendaten.json');
my $ips    = load_json_file('t/openehr2bff/in/ips_canonical.json');
my $lab    = load_json_file('t/openehr2bff/in/laboratory_report.json');
my $corona = load_json_file('t/openehr2bff/in/compo_corona.json');

sub with_subject_id {
    my ( $composition, $id ) = @_;
    my %copy = %{$composition};
    $copy{subject} = {
        _type        => 'PARTY_SELF',
        external_ref => {
            id        => { _type => 'GENERIC_ID', value => $id, scheme => 'PMI' },
            namespace => 'PMI',
            type      => 'PERSON',
        },
    };
    return \%copy;
}

sub rewrite_admin_gender {
    my ( $node, $name, $code ) = @_;
    return unless defined $node;

    if ( ref($node) eq 'HASH' ) {
        if ( exists $node->{name}
            && ref( $node->{name} ) eq 'HASH'
            && defined $node->{name}{value}
            && $node->{name}{value} =~ /Administratives Geschlecht/i
            && exists $node->{value}
            && ref( $node->{value} ) eq 'HASH'
            && exists $node->{value}{defining_code}
            && ref( $node->{value}{defining_code} ) eq 'HASH' )
        {
            $node->{name}{value} = $name if defined $name;
            $node->{value}{defining_code}{code_string} = $code if defined $code;
            return 1;
        }

        for my $value ( values %{$node} ) {
            my $updated = rewrite_admin_gender( $value, $name, $code );
            return 1 if $updated;
        }

        return 0;
    }

    if ( ref($node) eq 'ARRAY' ) {
        for my $entry ( @{$node} ) {
            my $updated = rewrite_admin_gender( $entry, $name, $code );
            return 1 if $updated;
        }
    }

    return 0;
}

subtest 'openehr2bff aggregates canonical compositions into one individual' => sub {
    my $convert = build_convert(
        method      => 'openehr2bff',
        data        => {
            patient      => { id => 'openehr-patient-1' },
            compositions => [ $gender, $ips ],
        },
        in_textfile => 0,
    );

    my $individual = $convert->openehr2bff;

    is( $individual->{id}, 'openehr-patient-1', 'uses patient id from the envelope' );
    is( $individual->{sex}{id}, 'NCIT:C20197', 'maps administrative gender to Beacon sex term' );
    is( scalar @{ $individual->{info}{openehr}{compositions} }, 2, 'preserves all source compositions under info.openehr' );
};

subtest 'openehr2bff emits first-class arrays from multiple canonical compositions' => sub {
    my $convert = build_convert(
        method      => 'openehr2bff',
        data        => {
            patient      => { id => 'openehr-patient-2' },
            compositions => [ $gender, $ips, $lab, $corona ],
        },
        in_textfile => 0,
    );

    my $individual = $convert->openehr2bff;

    is( scalar @{ $individual->{diseases} }, 3, 'maps problem diagnosis entries to diseases' );
    is( scalar @{ $individual->{measures} }, 2, 'maps multiple observations with values to measures' );
    is( scalar @{ $individual->{phenotypicFeatures} }, 7, 'maps symptom screening observations to phenotypicFeatures' );
    is( scalar @{ $individual->{interventionsOrProcedures} }, 1, 'maps procedure actions to interventionsOrProcedures' );
    is( scalar @{ $individual->{treatments} }, 2, 'maps medication actions to treatments' );

    my ($loinc_measure) = grep {
        exists $_->{assayCode}
          && ref( $_->{assayCode} ) eq 'HASH'
          && exists $_->{assayCode}{id}
          && $_->{assayCode}{id} eq 'LOINC:2093-3'
    } @{ $individual->{measures} };

    ok( defined $loinc_measure, 'keeps coded laboratory observations as first-class measures' );
    is( $loinc_measure->{measurementValue}{quantity}{value}, 203, 'preserves numeric result values for coded lab measures' );

    my ($present_feature) = grep { exists $_->{excluded} && $_->{excluded} == 0 }
      @{ $individual->{phenotypicFeatures} };
    my ($absent_feature) = grep { exists $_->{excluded} && $_->{excluded} == 1 }
      @{ $individual->{phenotypicFeatures} };

    ok( defined $present_feature, 'marks present symptoms as non-excluded phenotypic features' );
    ok( defined $absent_feature, 'marks absent symptoms as excluded phenotypic features' );

    my $tmp_file = temp_output_file( suffix => '.json', dir => 't' );
    write_json_file( $tmp_file, [$individual] );
    ok(
        structured_files_match( 't/openehr2bff/out/individuals.json', $tmp_file ),
        'matches the openEHR fixture snapshot'
    );
};

subtest 'openehr2bff accepts openEHR ehr_id and ehr_status patient identifiers' => sub {
    {
        my $convert = build_convert(
            method      => 'openehr2bff',
            data        => {
                ehr_id       => { value => 'ehr-123' },
                compositions => [$gender],
            },
            in_textfile => 0,
        );

        my $individual = $convert->openehr2bff;
        is( $individual->{id}, 'ehr-123', 'uses ehr_id.value when present in the payload envelope' );
    }

    {
        my $convert = build_convert(
            method      => 'openehr2bff',
            data        => {
                ehr_id       => { value => 'ehr-123' },
                ehr_status   => {
                    subject => {



( run in 1.815 second using v1.01-cache-2.11-cpan-98e64b0badf )