variant-linker
Version:
CLI for Ensembl VEP and Variant Recoder
159 lines (139 loc) • 6.3 kB
JavaScript
// src/inheritance/segregationChecker.js
;
/**
* @fileoverview Checks if a variant segregates with affected status in a pedigree.
* @module segregationChecker
*/
const debug = require('debug')('variant-linker:inheritance:segregation');
const debugDetailed = require('debug')('variant-linker:detailed');
const { isVariant, isRef, isMissing } = require('./genotypeUtils');
/**
* Checks if a variant segregates according to a given pattern within a pedigree.
* Focuses on basic segregation: do all affected individuals have the variant?
* Can be extended for pattern-specific checks (e.g., unaffected carriers for AD).
*
* @param {string} pattern - The inheritance pattern being checked (e.g., 'autosomal_dominant').
* Currently used mainly for logging context.
* @param {Map<string, string>} genotypes - Map of sampleId to genotype string for the variant.
* @param {Map<string, Object>} pedigreeData - Parsed pedigree data containing affected status.
* @returns {string} Segregation status: 'segregates', 'does_not_segregate', or 'unknown_*'.
*/
function checkSegregation(pattern, genotypes, pedigreeData) {
debugDetailed(`--- Entering checkSegregation for pattern: ${pattern} ---`);
if (!pedigreeData || pedigreeData.size === 0) {
debug('No pedigree data available for segregation check');
debugDetailed(`--- Exiting checkSegregation. Result: unknown_missing_data ---`);
return 'unknown_missing_data';
}
if (!genotypes || genotypes.size === 0) {
debug('No genotype data available for segregation check');
debugDetailed(`--- Exiting checkSegregation. Result: unknown_missing_data ---`);
return 'unknown_missing_data';
}
// --- Identify Affected and Unaffected with Genotypes ---
const affectedWithGenotype = [];
const unaffectedWithGenotype = [];
let affectedCount = 0;
let hasAnyAffectedGenotype = false;
for (const [sampleId, pedInfo] of pedigreeData.entries()) {
const isAffected = pedInfo.affectedStatus === '2' || pedInfo.affectedStatus === 2;
if (isAffected) affectedCount++;
if (genotypes.has(sampleId)) {
const gt = genotypes.get(sampleId);
const data = { sampleId, gt };
if (isAffected) {
affectedWithGenotype.push(data);
if (!isMissing(gt)) hasAnyAffectedGenotype = true;
} else if (pedInfo.affectedStatus === '1' || pedInfo.affectedStatus === 1) {
unaffectedWithGenotype.push(data);
}
}
}
// Check if there are any affected individuals in the pedigree at all
if (affectedCount === 0) {
debug('No affected individuals found in the pedigree.');
debugDetailed(`--- Exiting checkSegregation. Result: unknown_no_affected ---`);
return 'unknown_no_affected';
}
// Check if we have genotype data for *any* affected individual
if (!hasAnyAffectedGenotype) {
debug('No non-missing genotype data available for any affected individuals.');
debugDetailed(`--- Exiting checkSegregation. Result: unknown_missing_data ---`);
return 'unknown_missing_data'; // Cannot determine segregation without affected genotypes
}
// --- Perform Segregation Checks ---
let affectedHaveVariantCount = 0;
let affectedLackVariantCount = 0;
let affectedMissingGtCount = 0;
let unaffectedHaveVariantCount = 0;
for (const { gt } of affectedWithGenotype) {
if (isVariant(gt)) {
affectedHaveVariantCount++;
} else if (isRef(gt)) {
affectedLackVariantCount++;
} else if (isMissing(gt)) {
affectedMissingGtCount++;
}
}
for (const { gt } of unaffectedWithGenotype) {
if (isVariant(gt)) {
unaffectedHaveVariantCount++;
}
}
debugDetailed(
`Segregation Counts: Affected w/ Variant=${affectedHaveVariantCount}, ` +
`Affected w/o Variant=${affectedLackVariantCount}, ` +
`Affected w/ Missing GT=${affectedMissingGtCount}, ` +
`Unaffected w/ Variant=${unaffectedHaveVariantCount}`
);
// --- Determine Segregation Status ---
// Condition 1: Does any affected individual definitively *lack* the variant?
if (affectedLackVariantCount > 0) {
debug(
`${pattern} does not segregate: ${affectedLackVariantCount} affected individual(s) ` +
`are reference homozygous.`
);
debugDetailed(`--- Exiting checkSegregation. Result: does_not_segregate ---`);
return 'does_not_segregate';
}
// Condition 2: Check for incomplete penetrance (unaffected with variant)
// For stricter segregation (esp. dominant), this could be 'does_not_segregate'.
// Report 'segregates' if Condition 1 is false, but note penetrance.
if (unaffectedHaveVariantCount > 0) {
debug(
`Potential incomplete penetrance for ${pattern}: ` +
`${unaffectedHaveVariantCount} unaffected individual(s) have the variant.`
);
// Decide if this breaks segregation based on strictness required.
// For now, allow segregation if affected individuals have the variant.
// Consider adding a different status like 'segregates_with_incomplete_penetrance'?
}
// Condition 3: Do all genotyped affected individuals have the variant?
// (Requires at least one affected to have variant and none to lack it)
// We already checked affectedLackVariantCount == 0
if (affectedHaveVariantCount > 0) {
// Missing genotypes in affected: less certain but possible.
if (affectedMissingGtCount > 0) {
debug(
`${pattern} likely segregates, but ${affectedMissingGtCount} affected individual(s) ` +
`have missing genotypes.`
);
} else {
debug(`${pattern} segregates consistently with disease status.`);
}
debugDetailed(`--- Exiting checkSegregation. Result: segregates ---`);
return 'segregates';
}
// Fallback: No affected had variant (implies all missing GT)
// or other conditions weren't met. This was covered by hasAnyAffectedGenotype check earlier.
// Unreachable if hasAnyAffectedGenotype=true and affectedLackVariantCount=0
// Add a safeguard log.
debug(
`Segregation unclear for ${pattern}. Have=${affectedHaveVariantCount}, ` +
`Lack=${affectedLackVariantCount}, HasGT=${hasAnyAffectedGenotype}`
);
return 'unknown_missing_data'; // Default to unknown if logic fails
}
module.exports = {
checkSegregation,
};