Merge pull request 'fix: COI correctly calculates parent×offspring and direct-relation pairings' (#47) from fix/coi-direct-relation into master
Reviewed-on: #47
This commit was merged in pull request #47.
This commit is contained in:
@@ -57,7 +57,15 @@ function isDirectRelation(db, sireId, damId) {
|
||||
* calculateCOI(db, sireId, damId)
|
||||
* Wright Path Coefficient method.
|
||||
* Dogs included at gen 0 in their own maps so parent x offspring
|
||||
* yields 25% COI. Per path: (0.5)^(sireGen + damGen + 1)
|
||||
* yields ~25% COI.
|
||||
*
|
||||
* Fix: do NOT exclude sid/did from commonIds globally.
|
||||
* - Exclude `did` from sireMap keys (the dam itself can't be a
|
||||
* common ancestor of the sire's side for THIS pairing's offspring)
|
||||
* - Exclude `sid` from damMap keys (same logic for sire)
|
||||
* This preserves the case where the sire IS a common ancestor in the
|
||||
* dam's ancestry (parent x offspring) while still avoiding reflexive
|
||||
* self-loops.
|
||||
*/
|
||||
function calculateCOI(db, sireId, damId) {
|
||||
const sid = parseInt(sireId);
|
||||
@@ -65,8 +73,15 @@ function calculateCOI(db, sireId, damId) {
|
||||
const sireMap = getAncestorMap(db, sid);
|
||||
const damMap = getAncestorMap(db, did);
|
||||
|
||||
// Common ancestors: in BOTH maps, but:
|
||||
// - not the dam itself appearing in sireMap (would be a loop)
|
||||
// - not the sire itself appearing in damMap already handled below
|
||||
// We collect all IDs present in both, excluding only the direct
|
||||
// subjects (did from sireMap side, sid excluded already since we
|
||||
// iterate sireMap keys — but sid IS in sireMap at gen 0, and if
|
||||
// damMap also has sid, that is the parent×offspring case we WANT).
|
||||
const commonIds = [...sireMap.keys()].filter(
|
||||
id => damMap.has(id) && id !== sid && id !== did
|
||||
id => damMap.has(id) && id !== did
|
||||
);
|
||||
|
||||
let coi = 0;
|
||||
@@ -103,11 +118,11 @@ function calculateCOI(db, sireId, damId) {
|
||||
};
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// =====================================================================
|
||||
// IMPORTANT: Specific named routes MUST be registered BEFORE
|
||||
// the /:id wildcard, or Express will match 'relations' and
|
||||
// 'trial-pairing' as dog IDs and return 404/wrong data.
|
||||
// =============================================================
|
||||
// =====================================================================
|
||||
|
||||
// POST /api/pedigree/trial-pairing
|
||||
router.post('/trial-pairing', (req, res) => {
|
||||
@@ -122,7 +137,7 @@ router.post('/trial-pairing', (req, res) => {
|
||||
const dam = db.prepare("SELECT * FROM dogs WHERE id = ? AND sex = 'female'").get(dam_id);
|
||||
|
||||
if (!sire || !dam) {
|
||||
return res.status(404).json({ error: 'Invalid sire or dam — check sex values in database' });
|
||||
return res.status(404).json({ error: 'Invalid sire or dam \u2014 check sex values in database' });
|
||||
}
|
||||
|
||||
const relation = isDirectRelation(db, sire_id, dam_id);
|
||||
@@ -153,9 +168,9 @@ router.get('/relations/:sireId/:damId', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// =====================================================================
|
||||
// Wildcard routes last
|
||||
// =============================================================
|
||||
// =====================================================================
|
||||
|
||||
// GET /api/pedigree/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user