diff --git a/GerbilManagerWebAPI/Controllers/InbreedingController.cs b/GerbilManagerWebAPI/Controllers/InbreedingController.cs
new file mode 100644
index 0000000..45a7a76
--- /dev/null
+++ b/GerbilManagerWebAPI/Controllers/InbreedingController.cs
@@ -0,0 +1,39 @@
+using GerbilManagerWebAPI.DAL;
+using GerbilManagerWebAPI.Dtos;
+using GerbilManagerWebAPI.Genetics;
+using Microsoft.AspNetCore.Mvc;
+
+namespace GerbilManagerWebAPI.Controllers
+{
+ ///
+ /// Inbreeding-coefficient (Inzuchtkoeffizient) endpoints — both for an existing
+ /// gerbil and for a hypothetical pairing (Probeverpaarung).
+ ///
+ [ApiController]
+ public class InbreedingController : ControllerBase
+ {
+ private readonly InbreedingService service;
+
+ // ApplicationContext is already registered in DI; the service is a thin,
+ // dependency-free wrapper so it needs no separate registration in Program.cs.
+ public InbreedingController(ApplicationContext db)
+ {
+ service = new InbreedingService(db);
+ }
+
+ // GET /gerbils/{id}/inbreeding-coefficient
+ [HttpGet("gerbils/{id}/inbreeding-coefficient")]
+ public ActionResult GetForGerbil(Guid id)
+ {
+ var result = service.ForGerbil(id);
+ return result is null ? NotFound() : result;
+ }
+
+ // POST /genetics/test-inbreeding
+ [HttpPost("genetics/test-inbreeding")]
+ public ActionResult TestPairing(TestInbreedingDto dto)
+ {
+ return service.ForPairing(dto.FatherId, dto.MotherId);
+ }
+ }
+}
diff --git a/GerbilManagerWebAPI/Dtos/TestInbreedingDto.cs b/GerbilManagerWebAPI/Dtos/TestInbreedingDto.cs
new file mode 100644
index 0000000..dacdf96
--- /dev/null
+++ b/GerbilManagerWebAPI/Dtos/TestInbreedingDto.cs
@@ -0,0 +1,9 @@
+namespace GerbilManagerWebAPI.Dtos
+{
+ ///
+ /// Request body for a hypothetical pairing's inbreeding coefficient
+ /// (Inzuchtkoeffizient der geplanten Verpaarung). Either parent may be omitted,
+ /// in which case the coefficient is 0.
+ ///
+ public record TestInbreedingDto(Guid? FatherId, Guid? MotherId);
+}
diff --git a/GerbilManagerWebAPI/Genetics/InbreedingResult.cs b/GerbilManagerWebAPI/Genetics/InbreedingResult.cs
new file mode 100644
index 0000000..d2d5b6e
--- /dev/null
+++ b/GerbilManagerWebAPI/Genetics/InbreedingResult.cs
@@ -0,0 +1,25 @@
+namespace GerbilManagerWebAPI.Genetics
+{
+ ///
+ /// Result of an inbreeding-coefficient (Inzuchtkoeffizient) calculation for an
+ /// individual or a hypothetical pairing.
+ ///
+ /// Wright's inbreeding coefficient F, in the range 0..1.
+ /// The coefficient expressed as a percentage (F * 100).
+ ///
+ /// The deepest generation of known ancestry above the individual (parents = 1,
+ /// grandparents = 2, …). 0 when no parents are recorded.
+ ///
+ ///
+ /// The ancestors common to both parents, each with its additive contribution to F,
+ /// ordered by contribution descending. Empty when there is no inbreeding.
+ ///
+ public record InbreedingResult(
+ double Coefficient,
+ double Percent,
+ int GenerationsAvailable,
+ IReadOnlyList CommonAncestors);
+
+ /// A single common ancestor and how much it contributes to F.
+ public record CommonAncestorContribution(Guid Id, string Name, double Contribution);
+}
diff --git a/GerbilManagerWebAPI/Genetics/InbreedingService.cs b/GerbilManagerWebAPI/Genetics/InbreedingService.cs
new file mode 100644
index 0000000..7ba7c19
--- /dev/null
+++ b/GerbilManagerWebAPI/Genetics/InbreedingService.cs
@@ -0,0 +1,74 @@
+using GerbilManagerWebAPI.Models;
+using Microsoft.EntityFrameworkCore;
+
+namespace GerbilManagerWebAPI.Genetics
+{
+ ///
+ /// Loads the pedigree out of the database into in-memory lookups (the home dataset
+ /// is small) and delegates the actual maths to .
+ ///
+ /// Parent links live in EF shadow foreign keys: a gerbil's birth litter is
+ /// Gerbils.LitterId; that litter's parents are Litters.FatherId and
+ /// Litters.MotherId.
+ ///
+ public sealed class InbreedingService
+ {
+ private readonly ApplicationContext _db;
+
+ public InbreedingService(ApplicationContext db) => _db = db;
+
+ /// F for an existing gerbil, or null if no such gerbil exists.
+ public InbreedingResult? ForGerbil(Guid id)
+ {
+ var calculator = BuildCalculator();
+ return calculator.Contains(id) ? calculator.ForIndividual(id) : null;
+ }
+
+ /// F for the hypothetical offspring of the given sire and dam.
+ public InbreedingResult ForPairing(Guid? fatherId, Guid? motherId)
+ {
+ return BuildCalculator().ForOffspringOf(fatherId, motherId);
+ }
+
+ private PedigreeCalculator BuildCalculator()
+ {
+ var litters = _db.Set()
+ .Select(l => new
+ {
+ l.Id,
+ FatherId = EF.Property(l, "FatherId"),
+ MotherId = EF.Property(l, "MotherId"),
+ })
+ .ToDictionary(l => l.Id, l => (l.FatherId, l.MotherId));
+
+ var gerbils = _db.Set()
+ .Select(g => new
+ {
+ g.Id,
+ g.Name,
+ LitterId = EF.Property(g, "LitterId"),
+ })
+ .ToList();
+
+ var parents = new Dictionary(gerbils.Count);
+ var names = new Dictionary(gerbils.Count);
+
+ foreach (var g in gerbils)
+ {
+ names[g.Id] = g.Name;
+
+ Guid? father = null;
+ Guid? mother = null;
+ if (g.LitterId is Guid litterId && litters.TryGetValue(litterId, out var litterParents))
+ {
+ father = litterParents.FatherId;
+ mother = litterParents.MotherId;
+ }
+
+ parents[g.Id] = (father, mother);
+ }
+
+ return new PedigreeCalculator(parents, names);
+ }
+ }
+}
diff --git a/GerbilManagerWebAPI/Genetics/PedigreeCalculator.cs b/GerbilManagerWebAPI/Genetics/PedigreeCalculator.cs
new file mode 100644
index 0000000..90dd751
--- /dev/null
+++ b/GerbilManagerWebAPI/Genetics/PedigreeCalculator.cs
@@ -0,0 +1,219 @@
+namespace GerbilManagerWebAPI.Genetics
+{
+ ///
+ /// Computes the inbreeding coefficient (Inzuchtkoeffizient) using Wright's path
+ /// method:
+ ///
+ /// F = Σ over common ancestors, over every valid path, of
+ /// (1/2)^(n1 + n2 + 1) * (1 + F_A)
+ ///
+ /// where, for a given common ancestor A, n1 is the number of links from the sire
+ /// up to A and n2 the number of links from the dam up to A, and F_A is A's own
+ /// inbreeding coefficient (computed recursively at full available pedigree depth).
+ /// A path counts only if the sire-side and dam-side chains share no individual
+ /// other than A — this is what prevents double counting.
+ ///
+ /// This type is deliberately free of any database / EF dependency: it works purely
+ /// from a parent lookup, so it can be unit-tested with hand-built pedigrees.
+ ///
+ public sealed class PedigreeCalculator
+ {
+ private readonly IReadOnlyDictionary _parents;
+ private readonly IReadOnlyDictionary _names;
+
+ // F_A memo + re-entrancy guard (the latter only matters if the data somehow
+ // contains a cycle, which is biologically impossible but cheap to defend).
+ private readonly Dictionary _fCache = new();
+ private readonly HashSet _fInProgress = new();
+ private readonly Dictionary _depthCache = new();
+
+ public PedigreeCalculator(
+ IReadOnlyDictionary parents,
+ IReadOnlyDictionary names)
+ {
+ _parents = parents;
+ _names = names;
+ }
+
+ /// True if the given individual exists in the pedigree.
+ public bool Contains(Guid id) => _parents.ContainsKey(id);
+
+ /// Inbreeding coefficient of an existing individual.
+ public InbreedingResult ForIndividual(Guid id)
+ {
+ var (father, mother) = ParentsOf(id);
+ return ForOffspringOf(father, mother);
+ }
+
+ ///
+ /// Inbreeding coefficient of the (possibly hypothetical) offspring of the given
+ /// sire and dam — equivalently, the kinship between the two parents.
+ ///
+ public InbreedingResult ForOffspringOf(Guid? sire, Guid? dam)
+ {
+ int generations = GenerationsAvailable(sire, dam);
+
+ if (sire is not Guid s || dam is not Guid d)
+ {
+ // A parent is unknown → no inbreeding can be established.
+ return new InbreedingResult(0.0, 0.0, generations, Array.Empty());
+ }
+
+ var contributions = Coefficient(s, d);
+ double f = contributions.Values.Sum();
+
+ var commonAncestors = contributions
+ .Select(kv => new CommonAncestorContribution(kv.Key, NameOf(kv.Key), kv.Value))
+ .OrderByDescending(c => c.Contribution)
+ .ThenBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ return new InbreedingResult(f, f * 100.0, generations, commonAncestors);
+ }
+
+ ///
+ /// Per-common-ancestor contributions to the kinship of
+ /// and . The sum of the values is the coefficient.
+ ///
+ private Dictionary Coefficient(Guid sire, Guid dam)
+ {
+ var sirePaths = EnumerateAncestorPaths(sire);
+ var damPaths = EnumerateAncestorPaths(dam);
+
+ // Group the dam's paths by the ancestor they terminate at for quick lookup.
+ var damPathsByAncestor = damPaths
+ .GroupBy(p => p[^1])
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ var contributions = new Dictionary();
+
+ foreach (var sirePath in sirePaths)
+ {
+ var ancestor = sirePath[^1];
+ if (!damPathsByAncestor.TryGetValue(ancestor, out var matchingDamPaths))
+ {
+ continue;
+ }
+
+ var sireNodes = new HashSet(sirePath);
+
+ foreach (var damPath in matchingDamPaths)
+ {
+ // The two chains may meet only at the common ancestor itself.
+ if (damPath.Any(node => node != ancestor && sireNodes.Contains(node)))
+ {
+ continue;
+ }
+
+ int n1 = sirePath.Count - 1;
+ int n2 = damPath.Count - 1;
+ double term = Math.Pow(0.5, n1 + n2 + 1) * (1.0 + F(ancestor));
+
+ contributions[ancestor] = contributions.GetValueOrDefault(ancestor) + term;
+ }
+ }
+
+ return contributions;
+ }
+
+ /// Inbreeding coefficient F of a single individual (memoized).
+ private double F(Guid id)
+ {
+ if (_fCache.TryGetValue(id, out var cached))
+ {
+ return cached;
+ }
+
+ // Defend against accidental cycles in the data.
+ if (!_fInProgress.Add(id))
+ {
+ return 0.0;
+ }
+
+ double f = 0.0;
+ var (father, mother) = ParentsOf(id);
+ if (father is Guid s && mother is Guid d)
+ {
+ f = Coefficient(s, d).Values.Sum();
+ }
+
+ _fInProgress.Remove(id);
+ _fCache[id] = f;
+ return f;
+ }
+
+ ///
+ /// Every chain of parent links starting at , including
+ /// the trivial chain [start] itself. Each returned list runs from the start
+ /// individual down to one of its ancestors (inclusive). A visited set keeps the
+ /// walk finite even if the data contains a cycle.
+ ///
+ private List> EnumerateAncestorPaths(Guid start)
+ {
+ var all = new List>();
+ var path = new List();
+
+ void Walk(Guid node)
+ {
+ path.Add(node);
+ all.Add(new List(path));
+
+ var (father, mother) = ParentsOf(node);
+ foreach (var parent in new[] { father, mother })
+ {
+ if (parent is Guid p && !path.Contains(p))
+ {
+ Walk(p);
+ }
+ }
+
+ path.RemoveAt(path.Count - 1);
+ }
+
+ Walk(start);
+ return all;
+ }
+
+ ///
+ /// Deepest generation of known ancestry for the offspring of the two parents
+ /// (parents = 1, grandparents = 2, …). 0 when neither parent is known.
+ ///
+ private int GenerationsAvailable(Guid? sire, Guid? dam)
+ {
+ int FromParent(Guid? parent) => parent is Guid p ? 1 + AncestorDepth(p) : 0;
+ return Math.Max(FromParent(sire), FromParent(dam));
+ }
+
+ /// Number of ancestral generations above (0 for a founder).
+ private int AncestorDepth(Guid id)
+ {
+ if (_depthCache.TryGetValue(id, out var cached))
+ {
+ return cached;
+ }
+
+ // Temporary 0 breaks any cycle while the real value is computed.
+ _depthCache[id] = 0;
+
+ var (father, mother) = ParentsOf(id);
+ int depth = 0;
+ if (father is Guid f)
+ {
+ depth = Math.Max(depth, 1 + AncestorDepth(f));
+ }
+ if (mother is Guid m)
+ {
+ depth = Math.Max(depth, 1 + AncestorDepth(m));
+ }
+
+ _depthCache[id] = depth;
+ return depth;
+ }
+
+ private (Guid? Father, Guid? Mother) ParentsOf(Guid id) =>
+ _parents.TryGetValue(id, out var p) ? p : (null, null);
+
+ private string NameOf(Guid id) =>
+ _names.TryGetValue(id, out var name) ? name : id.ToString();
+ }
+}