UNPKG

variant-linker

Version:
195 lines (172 loc) 8.18 kB
// src/inheritance/patternPrioritizer.js 'use strict'; /** * @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) };