fix(auto-repair): preserve exact playbook candidates
This commit is contained in:
@@ -25,6 +25,7 @@ from src.models.playbook import (
|
||||
RiskLevel,
|
||||
SymptomPattern,
|
||||
)
|
||||
from src.services.playbook_rag import PlaybookMatch
|
||||
from src.services.playbook_service import PlaybookService
|
||||
from src.utils.timezone import now_taipei
|
||||
|
||||
@@ -282,6 +283,92 @@ class TestPlaybookService:
|
||||
# Should be empty or have very low similarity
|
||||
assert len(recommendations) == 0 or recommendations[0].similarity_score < 0.4
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_recommendations_prioritizes_exact_alert(self, service, mock_repo):
|
||||
"""Exact alert matches should win over fuzzy service matches."""
|
||||
exact_alert = create_test_playbook(
|
||||
playbook_id="PB-EXACT-ALERT",
|
||||
success_count=0,
|
||||
failure_count=0,
|
||||
)
|
||||
exact_alert.symptom_pattern.alert_names = ["AwoooPT16E"]
|
||||
exact_alert.symptom_pattern.affected_services = ["different-service"]
|
||||
competing_service = create_test_playbook(
|
||||
playbook_id="PB-FUZZY-SERVICE",
|
||||
success_count=20,
|
||||
failure_count=0,
|
||||
)
|
||||
competing_service.symptom_pattern.alert_names = ["SentryDown"]
|
||||
competing_service.symptom_pattern.affected_services = [
|
||||
"awoooi-auto-repair-canary-livefire"
|
||||
]
|
||||
await mock_repo.create(exact_alert)
|
||||
await mock_repo.create(competing_service)
|
||||
|
||||
symptoms = SymptomPattern(
|
||||
alert_names=["AwoooPT16E"],
|
||||
affected_services=["awoooi-auto-repair-canary-livefire"],
|
||||
severity_range=["P2"],
|
||||
)
|
||||
|
||||
recommendations = await service.get_recommendations(
|
||||
symptoms,
|
||||
top_k=1,
|
||||
use_rag=False,
|
||||
)
|
||||
|
||||
assert recommendations[0].playbook.playbook_id == "PB-EXACT-ALERT"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_recommendations_preserves_jaccard_candidates(
|
||||
self,
|
||||
service,
|
||||
mock_repo,
|
||||
):
|
||||
"""RAG hybrid top-k must not drop exact Jaccard candidates."""
|
||||
exact_alert = create_test_playbook(
|
||||
playbook_id="PB-EXACT-JACCARD",
|
||||
success_count=0,
|
||||
failure_count=0,
|
||||
)
|
||||
exact_alert.symptom_pattern.alert_names = ["AwoooPT16F"]
|
||||
exact_alert.symptom_pattern.affected_services = ["different-service"]
|
||||
competing_vector = create_test_playbook(
|
||||
playbook_id="PB-VECTOR-ONLY",
|
||||
success_count=20,
|
||||
failure_count=0,
|
||||
)
|
||||
competing_vector.symptom_pattern.alert_names = ["SentryDown"]
|
||||
competing_vector.symptom_pattern.affected_services = [
|
||||
"awoooi-auto-repair-canary-livefire"
|
||||
]
|
||||
await mock_repo.create(exact_alert)
|
||||
await mock_repo.create(competing_vector)
|
||||
|
||||
class FakeRagService:
|
||||
async def hybrid_search(self, **_kwargs):
|
||||
return [
|
||||
PlaybookMatch(
|
||||
playbook_id="PB-VECTOR-ONLY",
|
||||
similarity_score=0.99,
|
||||
match_type="vector",
|
||||
)
|
||||
]
|
||||
|
||||
async def fake_rag_service():
|
||||
return FakeRagService()
|
||||
|
||||
service._get_rag_service = fake_rag_service
|
||||
symptoms = SymptomPattern(
|
||||
alert_names=["AwoooPT16F"],
|
||||
affected_services=["awoooi-auto-repair-canary-livefire"],
|
||||
severity_range=["P2"],
|
||||
)
|
||||
|
||||
recommendations = await service.get_recommendations(symptoms, top_k=1)
|
||||
|
||||
assert recommendations[0].playbook.playbook_id == "PB-EXACT-JACCARD"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_playbook(self, service, mock_repo):
|
||||
"""Test approving a draft playbook"""
|
||||
|
||||
Reference in New Issue
Block a user