diff --git a/GerbilManager.Tests/GerbilManager.Tests.csproj b/GerbilManager.Tests/GerbilManager.Tests.csproj
new file mode 100644
index 0000000..d59000b
--- /dev/null
+++ b/GerbilManager.Tests/GerbilManager.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GerbilManager.Tests/PedigreeCalculatorTests.cs b/GerbilManager.Tests/PedigreeCalculatorTests.cs
new file mode 100644
index 0000000..efa12a4
--- /dev/null
+++ b/GerbilManager.Tests/PedigreeCalculatorTests.cs
@@ -0,0 +1,192 @@
+using GerbilManagerWebAPI.Genetics;
+
+namespace GerbilManager.Tests
+{
+ ///
+ /// Exact-math checks for the inbreeding coefficient (Inzuchtkoeffizient) against
+ /// the textbook reference values for canonical pedigrees.
+ ///
+ public class PedigreeCalculatorTests
+ {
+ private const int Precision = 10;
+
+ [Fact]
+ public void UnknownAncestry_IsZero()
+ {
+ var ped = new PedigreeBuilder();
+ var lonely = ped.Add("Lonely");
+ var calc = ped.Build();
+
+ var result = calc.ForIndividual(lonely);
+
+ Assert.Equal(0.0, result.Coefficient, Precision);
+ Assert.Equal(0, result.GenerationsAvailable);
+ Assert.Empty(result.CommonAncestors);
+ }
+
+ [Fact]
+ public void OneKnownParent_IsZero()
+ {
+ var ped = new PedigreeBuilder();
+ var sire = ped.Add("Sire");
+ var calc = ped.Build();
+
+ // Only one parent known → no inbreeding can be established.
+ var result = calc.ForOffspringOf(sire, null);
+
+ Assert.Equal(0.0, result.Coefficient, Precision);
+ Assert.Empty(result.CommonAncestors);
+ }
+
+ [Fact]
+ public void ParentOffspring_IsQuarter()
+ {
+ // Z is the offspring of P mated with its own daughter C.
+ var ped = new PedigreeBuilder();
+ var p = ped.Add("P");
+ var mate = ped.Add("Mate");
+ var c = ped.AddChild("C", p, mate);
+
+ var calc = ped.Build();
+ var result = calc.ForOffspringOf(p, c);
+
+ Assert.Equal(0.25, result.Coefficient, Precision);
+ Assert.Equal(25.0, result.Percent, Precision);
+ var ancestor = Assert.Single(result.CommonAncestors);
+ Assert.Equal(p, ancestor.Id);
+ Assert.Equal(0.25, ancestor.Contribution, Precision);
+ }
+
+ [Fact]
+ public void FullSiblings_IsQuarter()
+ {
+ // Parents S and D are full siblings (both children of GF and GM).
+ var ped = new PedigreeBuilder();
+ var gf = ped.Add("GF");
+ var gm = ped.Add("GM");
+ var s = ped.AddChild("S", gf, gm);
+ var d = ped.AddChild("D", gf, gm);
+
+ var calc = ped.Build();
+ var result = calc.ForOffspringOf(s, d);
+
+ Assert.Equal(0.25, result.Coefficient, Precision);
+ Assert.Equal(2, result.CommonAncestors.Count);
+ Assert.All(result.CommonAncestors, a => Assert.Equal(0.125, a.Contribution, Precision));
+ }
+
+ [Fact]
+ public void HalfSiblings_IsEighth()
+ {
+ // Parents S and D share only GF (other parents unrelated).
+ var ped = new PedigreeBuilder();
+ var gf = ped.Add("GF");
+ var x = ped.Add("X");
+ var y = ped.Add("Y");
+ var s = ped.AddChild("S", gf, x);
+ var d = ped.AddChild("D", gf, y);
+
+ var calc = ped.Build();
+ var result = calc.ForOffspringOf(s, d);
+
+ Assert.Equal(0.125, result.Coefficient, Precision);
+ var ancestor = Assert.Single(result.CommonAncestors);
+ Assert.Equal(gf, ancestor.Id);
+ Assert.Equal(0.125, ancestor.Contribution, Precision);
+ }
+
+ [Fact]
+ public void FirstCousins_IsSixteenth()
+ {
+ // Parents S and D are first cousins: their respective parents A1 and B1
+ // are full siblings (children of GF and GM).
+ var ped = new PedigreeBuilder();
+ var gf = ped.Add("GF");
+ var gm = ped.Add("GM");
+ var a1 = ped.AddChild("A1", gf, gm);
+ var b1 = ped.AddChild("B1", gf, gm);
+ var x = ped.Add("X");
+ var y = ped.Add("Y");
+ var s = ped.AddChild("S", a1, x);
+ var d = ped.AddChild("D", b1, y);
+
+ var calc = ped.Build();
+ var result = calc.ForOffspringOf(s, d);
+
+ Assert.Equal(0.0625, result.Coefficient, Precision);
+ Assert.Equal(2, result.CommonAncestors.Count);
+ Assert.All(result.CommonAncestors, a => Assert.Equal(0.03125, a.Contribution, Precision));
+ }
+
+ [Fact]
+ public void DeepCommonAncestry_SumsAllDisjointPaths()
+ {
+ // Full siblings S and D whose shared parents (GF, GM) are themselves full
+ // siblings (children of GGF, GGM). GF and GM are NOT inbred (their parents
+ // are unrelated founders, so F_GF = F_GM = 0), but the great-grandparents
+ // are reached by additional disjoint paths:
+ // GF + GM: 2 * (1/2)^3 = 0.25
+ // GGF: 2 * (1/2)^5 = 0.0625 (S-GF-GGF / D-GM-GGF and swap)
+ // GGM: 2 * (1/2)^5 = 0.0625
+ // ---------------------------------------------
+ // F = 0.375 (verified against the recursive kinship method)
+ var ped = new PedigreeBuilder();
+ var ggf = ped.Add("GGF");
+ var ggm = ped.Add("GGM");
+ var gf = ped.AddChild("GF", ggf, ggm);
+ var gm = ped.AddChild("GM", ggf, ggm);
+ var s = ped.AddChild("S", gf, gm);
+ var d = ped.AddChild("D", gf, gm);
+
+ var calc = ped.Build();
+ var result = calc.ForOffspringOf(s, d);
+
+ Assert.Equal(0.375, result.Coefficient, Precision);
+ // Four common ancestors: GF, GM, GGF, GGM.
+ Assert.Equal(4, result.CommonAncestors.Count);
+ Assert.Equal(0.375, result.CommonAncestors.Sum(a => a.Contribution), Precision);
+ // Deepest known generation above the offspring: S/D (1) → GF/GM (2) → GGF/GGM (3).
+ Assert.Equal(3, result.GenerationsAvailable);
+ }
+
+ [Fact]
+ public void GenerationsAvailable_CountsDeepestKnownGeneration()
+ {
+ var ped = new PedigreeBuilder();
+ var gf = ped.Add("GF");
+ var gm = ped.Add("GM");
+ var father = ped.AddChild("Father", gf, gm);
+ var mother = ped.Add("Mother");
+
+ var calc = ped.Build();
+ var result = calc.ForOffspringOf(father, mother);
+
+ // Father side reaches grandparents (2 generations); mother side reaches 1.
+ Assert.Equal(2, result.GenerationsAvailable);
+ }
+
+ /// Small helper to assemble a pedigree of plain GUIDs for the calculator.
+ private sealed class PedigreeBuilder
+ {
+ private readonly Dictionary _parents = new();
+ private readonly Dictionary _names = new();
+
+ public Guid Add(string name)
+ {
+ var id = Guid.NewGuid();
+ _parents[id] = (null, null);
+ _names[id] = name;
+ return id;
+ }
+
+ public Guid AddChild(string name, Guid father, Guid mother)
+ {
+ var id = Add(name);
+ _parents[id] = (father, mother);
+ return id;
+ }
+
+ public PedigreeCalculator Build() => new(_parents, _names);
+ }
+ }
+}
diff --git a/GerbilManager.slnx b/GerbilManager.slnx
index 763646b..1b2fb20 100644
--- a/GerbilManager.slnx
+++ b/GerbilManager.slnx
@@ -1,5 +1,6 @@
+