variant-linker
Version:
CLI for Ensembl VEP and Variant Recoder
195 lines (172 loc) • 8.18 kB
JavaScript
// src/inheritance/patternPrioritizer.js
;
/**
* @fileoverview Prioritizes inheritance patterns based on predefined rules and segregation results.
* @module patternPrioritizer
*/
const debugDetailed = require('debug')('variant-linker:detailed');
// Define the priority order for patterns. Lower index = higher priority.
// This list should include all possible patterns generated by patternDeducer.
const DEFAULT_PRIORITY_ORDER = [
// High Confidence / Strong Evidence
'de_novo',
'compound_heterozygous', // Confirmed CompHet should be high
'autosomal_recessive',
'x_linked_recessive',
// Medium Confidence / Likely / Possible based on data
'de_novo_candidate', // (Trio missing parent)
'compound_heterozygous_possible',
'compound_heterozygous_possible_missing_parents',
'compound_heterozygous_possible_missing_parent_genotypes',
'compound_heterozygous_possible_no_pedigree',
'autosomal_dominant', // AD often less stringent than recessive
'x_linked_dominant',
'autosomal_recessive_possible', // (Trio missing parent)
'x_linked_recessive_possible', // (Trio missing parent or unknown sex)
// Lower Confidence / Requires More Info / Observations
'autosomal_dominant_possible', // (Trio missing parent)
'x_linked_dominant_possible', // (Trio missing parent)
'homozygous', // Observation, could be AD/AR
'dominant', // General term, often from single sample or het index
'potential_x_linked', // Single sample on X chr
// Issues Identified / Lack of Mendelian Fit
'incomplete_penetrance', // Segregates but unaffected carriers exist
'incomplete_segregation', // Does not segregate (affected lack variant)
'non_mendelian', // Trio/PED check failed standard patterns
// Non-pathogenic / Likely Not Related / Unknown Status
'reference',
'non_causative_or_no_affected', // Variant present but no affected have it / no affected genotyped
// Error / Missing Data States (Lowest priority)
'unknown_with_missing_data', // Trio/PED checks hampered by missing GTs
'unknown_missing_trio_genotype',
'unknown_missing_ped_or_genotypes',
'unknown_no_affected_with_genotype',
'unknown_missing_genotypes', // General lack of GTs for variant
'unknown_missing_genotype', // Single sample missing GT
'unknown_no_affected', // No affected in PED
'error_checking_segregation',
'error_analysis_failed', // From orchestrator try/catch
'error_processing_failed', // From orchestrator try/catch
'error_unexpected_result_type', // From orchestrator
'error_no_index_sample', // From orchestrator
'error_missing_genotype_map', // From orchestrator
'unknown_not_processed', // From orchestrator
'unknown', // Generic fallback
];
/**
* Prioritizes inheritance patterns from a list of possibilities, considering segregation results.
*
* @param {Array<string>} possiblePatterns - List of possible inheritance patterns deduced.
* @param {Map<string, string>|null} segregationResults - Map of pattern to segregation status.
* Each value is one of: 'segregates', 'does_not_segregate',
* 'unknown_missing_data', 'unknown_no_affected'.
* Can be null if segregation not checked.
* @param {Array<string>} [priorityOrder=DEFAULT_PRIORITY_ORDER] - Optional custom priority order.
* @returns {string} The single highest-priority pattern based on rules and segregation.
*/
function prioritizePattern(
possiblePatterns,
segregationResults,
priorityOrder = DEFAULT_PRIORITY_ORDER
) {
debugDetailed(`--- Entering prioritizePattern ---`);
debugDetailed(
` Args: patterns=${JSON.stringify(possiblePatterns)}, ` +
`segregation=${JSON.stringify(Object.fromEntries(segregationResults || new Map()))}`
);
if (!possiblePatterns || possiblePatterns.length === 0) {
debugDetailed(
`--- Exiting prioritizePattern. Result: unknown (no possible patterns input) ---`
);
return 'unknown';
}
// Handle the single pattern case directly
if (possiblePatterns.length === 1) {
debugDetailed(
`--- Exiting prioritizePattern. Result: ${possiblePatterns[0]} (only one possibility) ---`
);
return possiblePatterns[0];
}
let patternsToConsider = [...possiblePatterns]; // Start with all possibilities
// --- Filter by Segregation ---
if (segregationResults && segregationResults.size > 0) {
const segregating = patternsToConsider.filter(
(p) => segregationResults.get(p) === 'segregates'
);
const unknownSegregation = patternsToConsider.filter(
(p) =>
segregationResults.get(p) === 'unknown_missing_data' ||
segregationResults.get(p) === 'unknown_no_affected' || // Treat 'no affected' as unknown for prioritization
!segregationResults.has(p) // Include patterns where segregation wasn't checked/applicable
);
const notSegregating = patternsToConsider.filter(
(p) => segregationResults.get(p) === 'does_not_segregate'
);
debugDetailed(
` Segregation: Segregating (${segregating.length}), ` +
`Unknown (${unknownSegregation.length}), Not Segregating (${notSegregating.length})`
);
if (segregating.length > 0) {
// Prioritize patterns that definitely segregate
patternsToConsider = segregating;
debugDetailed(
` -> Prioritizing segregating patterns: ${JSON.stringify(patternsToConsider)}`
);
} else if (unknownSegregation.length > 0) {
// If none segregate, prioritize those with unknown status (e.g., due to missing data)
patternsToConsider = unknownSegregation;
debugDetailed(
` -> Prioritizing unknown segregation patterns: ${JSON.stringify(patternsToConsider)}`
);
} else if (notSegregating.length > 0) {
// If all explicitly do not segregate, keep them all for sorting by intrinsic priority
debugDetailed(
` -> All patterns explicitly do not segregate. Keeping all for priority sort.`
);
patternsToConsider = notSegregating; // Keep the ones that failed segregation
} else {
// Fallback if filtering logic issue - keep original list
debugDetailed(` -> Unexpected state in segregation filtering. Keeping original list.`);
patternsToConsider = [...possiblePatterns];
}
} else {
debugDetailed(` No segregation results provided or available. Skipping segregation filter.`);
}
// --- Sort Remaining Patterns by Priority Order ---
patternsToConsider.sort((a, b) => {
const indexA = priorityOrder.indexOf(a);
const indexB = priorityOrder.indexOf(b);
// Handle patterns not in the priority list (put them last)
const effectiveIndexA = indexA === -1 ? Infinity : indexA;
const effectiveIndexB = indexB === -1 ? Infinity : indexB;
return effectiveIndexA - effectiveIndexB; // Lower index = higher priority
});
debugDetailed(` Patterns after sorting by priority: ${JSON.stringify(patternsToConsider)}`);
// --- Select the Top Pattern ---
let finalPattern = 'unknown'; // Default fallback
if (patternsToConsider.length > 0) {
finalPattern = patternsToConsider[0];
debugDetailed(` Selected highest priority pattern: ${finalPattern}`);
} else {
// This case should ideally not happen if input possiblePatterns was not empty
debugDetailed(` No patterns remained after filtering and sorting. Falling back.`);
// Fallback: return the highest priority from the original list if filtering removed everything
if (possiblePatterns.length > 0) {
possiblePatterns.sort((a, b) => {
const indexA = priorityOrder.indexOf(a);
const indexB = priorityOrder.indexOf(b);
const effectiveIndexA = indexA === -1 ? Infinity : indexA;
const effectiveIndexB = indexB === -1 ? Infinity : indexB;
return effectiveIndexA - effectiveIndexB;
});
finalPattern = possiblePatterns[0];
debugDetailed(` Fallback: Selecting highest priority from original list: ${finalPattern}`);
}
}
debugDetailed(`--- Exiting prioritizePattern. Result: ${finalPattern} ---`);
return finalPattern;
}
module.exports = {
prioritizePattern,
DEFAULT_PRIORITY_ORDER, // Export order if it might be needed elsewhere (e.g., UI)
};