diff --git a/client/src/App.jsx b/client/src/App.jsx
index 24285e0..42b5d3f 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -1,11 +1,12 @@
-import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'
-import { Home, Users, Activity, Heart } from 'lucide-react'
+import { BrowserRouter as Router, Routes, Route, Link} from 'react-router-dom'
+import { Home, Users, Activity, Heart, FlaskConical } from 'lucide-react'
import Dashboard from './pages/Dashboard'
import DogList from './pages/DogList'
import DogDetail from './pages/DogDetail'
import PedigreeView from './pages/PedigreeView'
import LitterList from './pages/LitterList'
import BreedingCalendar from './pages/BreedingCalendar'
+import PairingSimulator from './pages/PairingSimulator'
import './App.css'
function App() {
@@ -39,6 +40,10 @@ function App() {
Breeding
+
+
+ Pairing
+
@@ -51,6 +56,7 @@ function App() {
} />
} />
} />
+ } />
diff --git a/client/src/index.css b/client/src/index.css
index ee74197..11f6955 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -461,4 +461,33 @@ select {
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
-}
\ No newline at end of file
+}
+
+/* Risk Badge - Pairing Simulator */
+.risk-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.625rem 1.25rem;
+ border-radius: var(--radius);
+ font-size: 0.9375rem;
+ font-weight: 600;
+}
+
+.risk-low {
+ background: rgba(16, 185, 129, 0.15);
+ color: var(--success);
+ border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+.risk-med {
+ background: rgba(245, 158, 11, 0.15);
+ color: var(--warning);
+ border: 1px solid rgba(245, 158, 11, 0.3);
+}
+
+.risk-high {
+ background: rgba(239, 68, 68, 0.15);
+ color: var(--danger);
+ border: 1px solid rgba(239, 68, 68, 0.3);
+}
diff --git a/client/src/pages/PairingSimulator.jsx b/client/src/pages/PairingSimulator.jsx
new file mode 100644
index 0000000..9633f3e
--- /dev/null
+++ b/client/src/pages/PairingSimulator.jsx
@@ -0,0 +1,219 @@
+import { useState, useEffect } from 'react'
+import { FlaskConical, AlertTriangle, CheckCircle, XCircle, GitMerge } from 'lucide-react'
+
+export default function PairingSimulator() {
+ const [dogs, setDogs] = useState([])
+ const [sireId, setSireId] = useState('')
+ const [damId, setDamId] = useState('')
+ const [result, setResult] = useState(null)
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+ const [dogsLoading, setDogsLoading] = useState(true)
+
+ useEffect(() => {
+ fetch('/api/dogs')
+ .then(r => r.json())
+ .then(data => {
+ setDogs(Array.isArray(data) ? data : (data.dogs || []))
+ setDogsLoading(false)
+ })
+ .catch(() => setDogsLoading(false))
+ }, [])
+
+ 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
+ setLoading(true)
+ setError(null)
+ setResult(null)
+ try {
+ const res = await fetch('/api/pedigree/trial-pairing', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ 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())
+ } catch (err) {
+ setError(err.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ function RiskBadge({ coi, recommendation }) {
+ const isLow = coi < 5
+ const isMed = coi >= 5 && coi < 10
+ const isHigh = coi >= 10
+ return (
+
+ {isLow &&
}
+ {isMed &&
}
+ {isHigh &&
}
+
{recommendation}
+
+ )
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
Trial Pairing Simulator
+
+
+ Select a sire and dam to calculate the estimated inbreeding coefficient (COI) and view common ancestors.
+
+
+
+ {/* Selector Card */}
+
+
+ {/* Error */}
+ {error &&
{error}
}
+
+ {/* Results */}
+ {result && (
+
+ {/* COI Summary */}
+
+
+
+
Pairing
+
+ {result.sire.name}
+ ×
+ {result.dam.name}
+
+
+
+
COI
+
+ {result.coi.toFixed(2)}%
+
+
+
+
+
+
+
+
+
+ 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 5 generations. This pairing has excellent genetic diversity.
+
+ ) : (
+
+
+
+
+ | Ancestor |
+ Sire Gen |
+ Dam Gen |
+
+
+
+ {result.commonAncestors.map((anc, i) => (
+
+ | {anc.name} |
+
+ Gen {anc.sireGen}
+ |
+
+ Gen {anc.damGen}
+ |
+
+ ))}
+
+
+
+ )}
+
+
+ )}
+
+ )
+}