Convert-Pheno
view release on metacpan or search on metacpan
lib/Convert/Pheno/Audit/Terminology.pm view on Meta::CPAN
for my $index ( 0 .. $#COLUMNS ) {
$audit->write_string( 0, $index, $COLUMNS[$index], $header_format );
}
$audit->freeze_panes( 1, 3 );
$audit->set_selection( 1, 0 );
$audit->set_zoom(85);
my @widths = (
8, 18, 25, 28, 28, 28, 18, 32, 22, 14,
20, 20, 20, 18, 18, 16, 27, 29, 16, 20,
17, 20, 32, 22, 17, 17,
);
for my $index ( 0 .. $#widths ) {
$audit->set_column( $index, $index, $widths[$index] );
}
# Keep the default workbook focused on review while retaining all evidence
# for users who choose to unhide the technical columns.
my @hidden_columns = ( 6, 9 .. 15, 18 .. 21 );
for my $index (@hidden_columns) {
$audit->set_column( $index, $index, $widths[$index], undef, 1 );
}
$self->{format} = 'xlsx';
$self->{workbook} = $workbook;
$self->{summary_worksheet} = $summary;
$self->{audit_worksheet} = $audit;
$self->{header_format} = $header_format;
$self->{summary_label_format} = $summary_label_format;
$self->{score_format} = $score_format;
$self->{row_format} = \%row_format;
$self->{legend_format} = \%legend_format;
$self->{next_xlsx_row} = 1;
return 1;
}
sub _write_xlsx_row {
my ( $self, $row ) = @_;
die "XLSX terminology audits support at most "
. MAX_XLSX_DATA_ROWS
. " decisions; use .tsv.gz for larger runs\n"
if $self->{next_xlsx_row} > MAX_XLSX_DATA_ROWS;
my $worksheet = $self->{audit_worksheet};
my $xlsx_row = $self->{next_xlsx_row};
for my $index ( 0 .. $#COLUMNS ) {
my $column = $COLUMNS[$index];
my $value = $row->{$column};
next unless defined $value && length $value;
if ( $NUMERIC_COLUMN{$column} && _is_number($value) ) {
my $format =
$column eq 'best_candidate_score' || $column eq 'score_margin'
? $self->{score_format}
: undef;
$worksheet->write_number( $xlsx_row, $index, $value, $format );
}
else {
# Explicit string writes prevent source values beginning with '='
# from being interpreted as spreadsheet formulas.
$worksheet->write_string( $xlsx_row, $index, "$value" );
}
}
$self->{next_xlsx_row}++;
return 1;
}
sub _close_xlsx {
my ($self) = @_;
my $summary = $self->{summary_worksheet};
my $config = $self->{config};
my $counts = $self->{counts};
my @summary_rows = (
[ 'report_format', 'XLSX' ],
[ 'configured_search_mode', $config->{search} ],
[ 'text_similarity_method', $config->{text_similarity_method} ],
[ 'min_text_similarity_score', $config->{min_text_similarity_score} ],
[ 'levenshtein_weight', $config->{levenshtein_weight} ],
[ 'total_decisions', $self->{total_rows} ],
[ 'exact_or_direct', $counts->{resolved} ],
[ 'similarity_review', $counts->{review} ],
[ 'unresolved', $counts->{unresolved} ],
[ 'not_searched_or_source_fallback', $counts->{not_searched} ],
);
for my $index ( 0 .. $#summary_rows ) {
$summary->write_string(
$index + 1,
0,
$summary_rows[$index][0],
$self->{summary_label_format},
);
my $value = $summary_rows[$index][1];
if ( defined $value && _is_number($value) ) {
$summary->write_number( $index + 1, 1, $value );
}
else {
$summary->write_string( $index + 1, 1, defined $value ? "$value" : q{} );
}
}
my $legend_row = @summary_rows + 3;
$summary->write_string( $legend_row, 0, 'Review colors', $self->{summary_label_format} );
my @legend = (
[ resolved => 'Exact, direct, or configured resolution' ],
[ review => 'Similarity or spelling result to review' ],
[ unresolved => 'No term emitted' ],
[ not_searched => 'Not searched or source fallback' ],
);
for my $index ( 0 .. $#legend ) {
my ( $category, $description ) = @{ $legend[$index] };
$summary->write_string(
$legend_row + $index + 1,
0,
$category,
$self->{legend_format}{$category},
);
$summary->write_string( $legend_row + $index + 1, 1, $description );
}
$summary->write_string(
$legend_row + @legend + 2,
0,
'Interpretation',
$self->{summary_label_format},
);
$summary->write_string(
$legend_row + @legend + 2,
1,
'Colors prioritize review; they do not measure clinical confidence.',
);
$summary->write_string(
$legend_row + @legend + 4,
0,
'Workbook view',
$self->{summary_label_format},
);
$summary->write_string(
$legend_row + @legend + 4,
1,
'Technical audit columns are present but hidden by default.',
);
my $audit = $self->{audit_worksheet};
$audit->autofilter( 0, 0, $self->{total_rows}, $#COLUMNS );
if ( $self->{total_rows} ) {
my $last_row = $self->{total_rows} + 1;
my $range = "A2:Z$last_row";
my @rules = (
[ unresolved => '=$P2="not_found"' ],
[ not_searched => '=OR($P2="not_searched",$Q2="source_fallback")' ],
[ review => '=AND($P2<>"not_found",$P2<>"not_searched",$Q2<>"source_fallback",OR($T2="similarity",$V2="one_token_relaxed"))' ],
[ resolved => '=AND($P2<>"not_found",$P2<>"not_searched",$Q2<>"source_fallback",$T2<>"similarity",$V2<>"one_token_relaxed")' ],
);
for my $rule (@rules) {
$audit->conditional_formatting(
$range,
{
type => 'formula',
criteria => $rule->[1],
format => $self->{row_format}{ $rule->[0] },
}
);
}
}
$self->{workbook}->close()
or die "Failed to finalize XLSX terminology audit '$self->{path}'\n";
return 1;
}
sub _review_category {
my ($row) = @_;
return 'unresolved' if ( $row->{match_status} // q{} ) eq 'not_found';
return 'not_searched'
if ( $row->{match_status} // q{} ) eq 'not_searched'
|| ( $row->{decision_reason} // q{} ) eq 'source_fallback';
return 'review'
if ( $row->{lookup_resolution} // q{} ) eq 'similarity'
|| ( $row->{retrieval_path} // q{} ) eq 'one_token_relaxed';
return 'resolved';
}
sub _review_action {
my ($row) = @_;
my $category = _review_category($row);
return 'keep' if $category eq 'resolved';
return 'review_similarity' if $category eq 'review';
return 'resolve_or_accept_fallback' if $category eq 'unresolved';
return 'review_source_fallback';
}
sub _tsv_field {
my ($value) = @_;
return q{} unless defined $value;
$value =~ s/[\t\r\n]+/ /g;
return $value;
}
sub _is_number {
my ($value) = @_;
return defined $value
&& $value =~ /\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][+-]?\d+)?\z/;
}
1;
( run in 1.067 second using v1.01-cache-2.11-cpan-364913b4093 )