From 80b497e9021b35bc14d874877f12a210117caa84 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 11 Mar 2026 00:57:59 -0500 Subject: [PATCH] fix: PairingSimulator fetches /api/dogs?include_external=1 so external dogs appear in selectors --- client/src/pages/PairingSimulator.jsx | 304 +++++++++++--------------- 1 file changed, 130 insertions(+), 174 deletions(-) diff --git a/client/src/pages/PairingSimulator.jsx b/client/src/pages/PairingSimulator.jsx index 047f708..1dc8236 100644 --- a/client/src/pages/PairingSimulator.jsx +++ b/client/src/pages/PairingSimulator.jsx @@ -13,7 +13,8 @@ export default function PairingSimulator() { const [relationChecking, setRelationChecking] = useState(false) useEffect(() => { - fetch('/api/dogs') + // include_external=1 ensures external sires/dams appear for pairing + fetch('/api/dogs?include_external=1') .then(r => r.json()) .then(data => { setDogs(Array.isArray(data) ? data : (data.dogs || [])) @@ -54,9 +55,6 @@ export default function PairingSimulator() { checkRelation(sireId, val) } - const males = dogs.filter(d => d.sex === 'male') - const females = dogs.filter(d => d.sex === 'female') - async function handleSimulate(e) { e.preventDefault() if (!sireId || !damId) return @@ -64,16 +62,14 @@ export default function PairingSimulator() { setError(null) setResult(null) try { - const res = await fetch('/api/pedigree/trial-pairing', { + const res = await fetch('/api/pedigree/coi', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ sire_id: parseInt(sireId), dam_id: parseInt(damId) }) + body: JSON.stringify({ sire_id: parseInt(sireId), dam_id: parseInt(damId) }), }) - if (!res.ok) { - const err = await res.json() - throw new Error(err.error || 'Failed to calculate') - } - setResult(await res.json()) + const data = await res.json() + if (!res.ok) throw new Error(data.error || 'Simulation failed') + setResult(data) } catch (err) { setError(err.message) } finally { @@ -81,204 +77,164 @@ export default function PairingSimulator() { } } - function RiskBadge({ coi, recommendation }) { - const isLow = coi < 5 - const isMed = coi >= 5 && coi < 10 - const isHigh = coi >= 10 - return ( -
- {isLow && } - {isMed && } - {isHigh && } - {recommendation} -
- ) + const males = dogs.filter(d => d.sex === 'male') + const females = dogs.filter(d => d.sex === 'female') + + const coiColor = (coi) => { + if (coi < 0.0625) return 'var(--success)' + if (coi < 0.125) return 'var(--warning)' + return 'var(--danger)' + } + + const coiLabel = (coi) => { + if (coi < 0.0625) return 'Low' + if (coi < 0.125) return 'Moderate' + if (coi < 0.25) return 'High' + return 'Very High' } return ( -
- {/* Header */} -
-
-
- -
-

Trial Pairing Simulator

-
-

- Select a sire and dam to calculate the estimated inbreeding coefficient (COI) and view common ancestors. -

+
+
+ +

Pairing Simulator

+

+ Estimate the Coefficient of Inbreeding (COI) for a hypothetical pairing before breeding. + Includes both kennel and external dogs. +

- {/* Selector Card */} -
+
-
-
- - - {!dogsLoading && males.length === 0 && ( -

No male dogs registered.

+
+
+ + {dogsLoading ? ( +
Loading dogs...
+ ) : ( + )}
-
- - - {!dogsLoading && females.length === 0 && ( -

No female dogs registered.

+
+ + {dogsLoading ? ( +
Loading dogs...
+ ) : ( + )}
- {/* Direct-relation warning banner */} {relationChecking && ( -

Checking relationship…

+
+ Checking relationship... +
)} - {!relationChecking && relationWarning && ( + + {relationWarning && !relationChecking && (
- -
-

Direct Relation Detected

-

- {relationWarning}. COI will reflect the high inbreeding coefficient for this pairing. -

-
+ + Related: {relationWarning}
)}
- {/* Error */} - {error &&
{error}
} + {error && ( +
+
+ + Error: {error} +
+
+ )} - {/* Results */} {result && ( -
- {/* Direct-relation alert in results */} - {result.directRelation && ( -
- -
-

Direct Relation — High Inbreeding Risk

-

{result.directRelation}

+
+

+ Simulation Result +

+ +
+ {result.coi < 0.0625 + ? + : + } +
+
+ {(result.coi * 100).toFixed(2)}% +
+
+ COI — {coiLabel(result.coi)} +
+
+
+ + {result.common_ancestors && result.common_ancestors.length > 0 && ( +
+

+ Common Ancestors ({result.common_ancestors.length}) +

+
+ {result.common_ancestors.map((a, i) => ( + {a} + ))}
)} - {/* COI Summary */} -
-
-
-

Pairing

-

- {result.sire.name} - × - {result.dam.name} -

-
-
-

COI

-

- {result.coi.toFixed(2)}% -

-
+ {result.recommendation && ( +
+ {result.recommendation}
- -
- -
- -
- COI Guide: <5% Low risk · 5–10% Moderate risk · >10% High risk -
-
- - {/* Common Ancestors */} -
-
- -

Common Ancestors

- - {result.commonAncestors.length} found - -
- - {result.commonAncestors.length === 0 ? ( -

- No common ancestors found within 6 generations. This pairing has excellent genetic diversity. -

- ) : ( -
- - - - - - - - - - {result.commonAncestors.map((anc, i) => ( - - - - - - ))} - -
AncestorSire GenDam Gen
{anc.name} - Gen {anc.sireGen} - - Gen {anc.damGen} -
-
- )} -
+ )}
)}