/** * Transform API pedigree data to react-d3-tree format * @param {Object} dog - Dog object from API with nested sire/dam * @param {number} maxGenerations - Maximum generations to display (default 5) * @returns {Object} Tree data in react-d3-tree format */ export const transformPedigreeData = (dog, maxGenerations = 5) => { if (!dog) return null const buildTree = (dogData, generation = 0) => { if (!dogData || generation >= maxGenerations) { return null } const node = { name: dogData.name || 'Unknown', attributes: { id: dogData.id, sex: dogData.sex, registration: dogData.registration_number || '', birth_year: dogData.birth_date ? new Date(dogData.birth_date).getFullYear() : '' }, children: [] } // Add sire (father) to children if (dogData.sire) { const sireNode = buildTree(dogData.sire, generation + 1) if (sireNode) { node.children.push(sireNode) } } // Add dam (mother) to children if (dogData.dam) { const damNode = buildTree(dogData.dam, generation + 1) if (damNode) { node.children.push(damNode) } } // Remove empty children array if (node.children.length === 0) { delete node.children } return node } return buildTree(dog) } /** * Calculate total ancestors in pedigree * @param {Object} treeData - Tree data structure * @returns {number} Total number of ancestors */ export const countAncestors = (treeData) => { if (!treeData) return 0 let count = 1 if (treeData.children) { treeData.children.forEach(child => { count += countAncestors(child) }) } return count - 1 // Exclude the root dog } /** * Get generation counts * @param {Object} treeData - Tree data structure * @returns {Object} Generation counts { 1: count, 2: count, ... } */ export const getGenerationCounts = (treeData) => { const counts = {} const traverse = (node, generation = 0) => { if (!node) return counts[generation] = (counts[generation] || 0) + 1 if (node.children) { node.children.forEach(child => traverse(child, generation + 1)) } } traverse(treeData) delete counts[0] // Remove the root dog return counts } /** * Check if pedigree is complete for given generations * @param {Object} treeData - Tree data structure * @param {number} generations - Number of generations to check * @returns {boolean} True if complete */ export const isPedigreeComplete = (treeData, generations = 3) => { const expectedCount = Math.pow(2, generations) - 1 const actualCount = countAncestors(treeData) return actualCount >= expectedCount } /** * Find common ancestors between two dogs * @param {Object} dog1Tree - First dog's pedigree tree * @param {Object} dog2Tree - Second dog's pedigree tree * @returns {Array} Array of common ancestor IDs */ export const findCommonAncestors = (dog1Tree, dog2Tree) => { const getAncestorIds = (tree) => { const ids = new Set() const traverse = (node) => { if (!node) return if (node.attributes?.id) ids.add(node.attributes.id) if (node.children) { node.children.forEach(traverse) } } traverse(tree) return ids } const ids1 = getAncestorIds(dog1Tree) const ids2 = getAncestorIds(dog2Tree) return Array.from(ids1).filter(id => ids2.has(id)) } /** * Format COI value with risk level * @param {number} coi - Coefficient of Inbreeding * @returns {Object} { value, level, color, description } */ export const formatCOI = (coi) => { if (coi === null || coi === undefined) { return { value: 'N/A', level: 'unknown', color: '#6b7280', description: 'COI cannot be calculated' } } const value = coi.toFixed(2) if (coi <= 5) { return { value: `${value}%`, level: 'low', color: '#10b981', description: 'Low inbreeding - Excellent genetic diversity' } } else if (coi <= 10) { return { value: `${value}%`, level: 'medium', color: '#f59e0b', description: 'Moderate inbreeding - Acceptable with caution' } } else { return { value: `${value}%`, level: 'high', color: '#ef4444', description: 'High inbreeding - Consider genetic diversity' } } } /** * Get pedigree completeness percentage * @param {Object} treeData - Tree data structure * @param {number} targetGenerations - Target generations * @returns {number} Percentage complete (0-100) */ export const getPedigreeCompleteness = (treeData, targetGenerations = 5) => { const expectedTotal = Math.pow(2, targetGenerations) - 1 const actualCount = countAncestors(treeData) return Math.min(100, Math.round((actualCount / expectedTotal) * 100)) }