base.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # -*- coding: utf-8 -*-
  2. """
  3. Basis-Klasse für Arbitration-Strategien.
  4. Definiert das Interface für alle Strategien zur Satellite-Auswahl.
  5. """
  6. from abc import ABC, abstractmethod
  7. from typing import Any
  8. from trixy_core.arbitration.candidate import ArbitrationCandidate
  9. class ArbitrationStrategy(ABC):
  10. """
  11. Abstrakte Basisklasse für Arbitration-Strategien.
  12. Eine Strategie entscheidet, welcher Satellite bei gleichzeitiger
  13. Wakeword-Erkennung die Konversation führen soll.
  14. Beispiel:
  15. ```python
  16. class MyStrategy(ArbitrationStrategy):
  17. @property
  18. def name(self) -> str:
  19. return "my_strategy"
  20. def calculate_score(self, candidate: ArbitrationCandidate) -> float:
  21. return candidate.audio_level * 0.5 + candidate.confidence * 0.5
  22. def select(self, candidates: list[ArbitrationCandidate]) -> ArbitrationCandidate:
  23. return max(candidates, key=lambda c: self.calculate_score(c))
  24. ```
  25. """
  26. @property
  27. @abstractmethod
  28. def name(self) -> str:
  29. """
  30. Eindeutiger Name der Strategie.
  31. Returns:
  32. Name der Strategie (z.B. "loudest", "combined")
  33. """
  34. pass
  35. @property
  36. def description(self) -> str:
  37. """
  38. Beschreibung der Strategie.
  39. Returns:
  40. Menschenlesbare Beschreibung
  41. """
  42. return f"Arbitration-Strategie: {self.name}"
  43. @abstractmethod
  44. def calculate_score(
  45. self,
  46. candidate: ArbitrationCandidate,
  47. context: dict[str, Any] | None = None,
  48. ) -> float:
  49. """
  50. Berechnet den Score für einen Kandidaten.
  51. Args:
  52. candidate: Der zu bewertende Kandidat
  53. context: Optionaler Kontext (z.B. Raum-Informationen)
  54. Returns:
  55. Score zwischen 0.0 und 1.0 (höher = besser)
  56. """
  57. pass
  58. @abstractmethod
  59. def select(
  60. self,
  61. candidates: list[ArbitrationCandidate],
  62. context: dict[str, Any] | None = None,
  63. ) -> ArbitrationCandidate | None:
  64. """
  65. Wählt den besten Kandidaten aus.
  66. Args:
  67. candidates: Liste der Kandidaten
  68. context: Optionaler Kontext
  69. Returns:
  70. Ausgewählter Kandidat oder None wenn Liste leer
  71. """
  72. pass
  73. def rank(
  74. self,
  75. candidates: list[ArbitrationCandidate],
  76. context: dict[str, Any] | None = None,
  77. ) -> list[ArbitrationCandidate]:
  78. """
  79. Sortiert Kandidaten nach Score (absteigend).
  80. Args:
  81. candidates: Liste der Kandidaten
  82. context: Optionaler Kontext
  83. Returns:
  84. Sortierte Liste (bester zuerst)
  85. """
  86. if not candidates:
  87. return []
  88. # Scores berechnen
  89. for candidate in candidates:
  90. candidate.score = self.calculate_score(candidate, context)
  91. # Nach Score sortieren (absteigend)
  92. return sorted(candidates, key=lambda c: c.score, reverse=True)
  93. def validate_candidate(self, candidate: ArbitrationCandidate) -> bool:
  94. """
  95. Prüft ob Kandidat gültig für Auswahl ist.
  96. Kann von Subklassen überschrieben werden für
  97. zusätzliche Validierungslogik.
  98. Args:
  99. candidate: Zu prüfender Kandidat
  100. Returns:
  101. True wenn gültig
  102. """
  103. return (
  104. candidate.is_pending and
  105. candidate.audio_level >= 0.0 and
  106. candidate.confidence >= 0.0
  107. )
  108. def filter_valid(
  109. self,
  110. candidates: list[ArbitrationCandidate],
  111. ) -> list[ArbitrationCandidate]:
  112. """
  113. Filtert ungültige Kandidaten heraus.
  114. Args:
  115. candidates: Liste der Kandidaten
  116. Returns:
  117. Gefilterte Liste
  118. """
  119. return [c for c in candidates if self.validate_candidate(c)]
  120. def __repr__(self) -> str:
  121. return f"{self.__class__.__name__}(name={self.name!r})"